commit 41db46a8346d3397bf54bb02b1d2dc7010d78270 Author: 东皇 Date: Wed Aug 2 15:05:06 2017 +0800 first blood diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4cea3e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.* +!.gitignore +!.gitkeep +*.iml +*/src/main/resources/application.properties +*/src/main/resources/log4j2.xml +target/ +bin/ diff --git a/INSTALL.txt b/INSTALL.txt new file mode 100644 index 0000000..6cee3f8 --- /dev/null +++ b/INSTALL.txt @@ -0,0 +1,92 @@ +JA-SIG Central Authentication Service +Quick Installation Guide +---------------------------------- + +This guide is meant to be a quickstart for you configure and install CAS +as quickly and easily as possible. If you need more information, +you can check the CAS website at + +http://www.ja-sig.org/products/cas/ + +and you can subscribe to the CAS mailing list by visiting: + +http://www.ja-sig.org/products/cas/community/lists/ + +Quick Demo +---------- + +The following steps will deploy the included demo of CAS. Start here if +you're new to CAS and want to see it in action. These steps assume +you will be using Tomcat as your servlet container. + + +Quick Demo +---------- + +The following steps will deploy the included demo of CAS. Start here if +you're new to CAS and want to see it in action. These steps assume +you will be using Tomcat as your servlet container. + +1. copy modules/cas-server-webapp-VERSION.war to Tomcat's webapps/ directory + +2. start Tomcat + +3. access the CAS login page by opening up a web browser and visiting: + + http://hostname:8080/cas-server-webapp-VERSION/login (see note below) + + You should see the CAS login page asking you for your username and + password. The default authentication plugin accepts NetID=password. + Enter in an identical value for NetID and password and click LOGIN. + If everything is set up correctly, you should see a page stating that + you've successfully logged into CAS. Congratulations! + + Note: this URL assumes that port 8080 is not blocked by a firewall, + and Tomcat is configured to listen on that port (it is by default). + Since we are only testing CAS, this configuration uses http for transport + rather than https -- this is not something you would do in production. + +Customization +------------- + +After you've gotten CAS working, one of the first things you will want +to do is create your own skin. + +There is one skin included with CAS; Its called "default" If you look in cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui, +you will see that a skin is made up of the following files: + +casConfirmView.jsp displayed when the user is warned before being + redirected to the service +casGenericSuccess.jsp displayed when the user has been logged in without + providing a service to be redirected to +casLoginView.jsp the login form itself +casLogoutView.jsp displayed when the user has been logged out +serviceErrorView.jsp used in conjunction with the service registry feature, + displayed when the service the user is trying to + access is not allowed to use CAS. Note that this + feature is not enabled by default, in which case + all services are able to use CAS. + +Steps for creating a custom skin +-------------------------------- + Edit each of the pages to appear as you want them. Take care when + you're editing these pages not to change any of the forms or logic tags + unless you're sure you know what you're doing. If you need to include + a css file, put it in cas-server-webapp/src/main/webapp/themes/default/cas.css and reference it + with the following link tag: + + " type="text/css" + media="all" /> + + The "" will be replaced with the appropriate + URL to that directory. + +Documentation +--------------------------------- +Documentation for CAS can be found in the CAS User Manual: +http://www.ja-sig.org/wiki/display/CASUM/Home + +-------------------------- +Author: Drew Mazurek +Version: $Revision$ $Date$ +Since: 3.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c0f39a --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +cas-server-3.5.2 +================ + +对于JasigCAS 3.5.2版本进行简化,只保留基本的功能,用JDBC方式验证,并使用Metronic模板美化界面,以达到可以直接在项目中使用的目的。 + +关于代码改动内容,很早之前写过一篇文章,如下; +http://blog.csdn.net/puma_dong/article/details/11564309 + +cas的java客户端也进行了小调整,让登陆之后自动返回到登陆之前的页面,不再使用p:service定义的值,并有一个客户端配置单点登陆的例子,如下: + +https://github.com/pumadong/cas-client-3.2.1 diff --git a/assembly.xml b/assembly.xml new file mode 100644 index 0000000..15a7d21 --- /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-server-core/pom.xml b/cas-server-core/pom.xml new file mode 100644 index 0000000..e48c549 --- /dev/null +++ b/cas-server-core/pom.xml @@ -0,0 +1,255 @@ + + + + + + cas-server + org.jasig.cas + 3.5.2-SNAPSHOT + + 4.0.0 + org.jasig.cas + cas-server-core + Jasig CAS Core + CAS core + + + + log4j + log4j + ${log4j.version} + jar + compile + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + + + + com.github.inspektr + inspektr-audit + compile + + + + org.jasig.service.persondir + person-directory-impl + compile + + + + commons-codec + commons-codec + compile + + + + jdom + jdom + 1.0 + compile + + + + 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.hsqldb + hsqldb + 2.0.0 + test + + + + org.hibernate + hibernate-validator + test + + + + org.hibernate + hibernate-core + compile + + + + org.hibernate + hibernate-entitymanager + test + + + + javassist + javassist + test + 3.12.1.GA + + + + org.apache.santuario + xmlsec + 1.4.3 + runtime + + + commons-logging + commons-logging + + + + + + org.opensaml + opensaml + 2.5.1-1 + compile + + + org.slf4j + log4j-over-slf4j + + + joda-time + joda-time + + + + + + javax.xml + xmldsig + 1.0 + compile + + + + org.perf4j + perf4j + ${perf4j.version} + log4jonly + compile + + + + 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.opensymphony.quartz + quartz + 1.6.1 + test + jar + + + + com.github.inspektr + inspektr-support-spring + test + + + 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 0000000..8080405 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/CasVersion.java @@ -0,0 +1,48 @@ +/* + * 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. + */ + +package org.jasig.cas; + +/** + * Class that exposes the CAS version. Fetches the "Implementation-Version" + * manifest attribute from the jar file. + * + * @author Dmitriy Kopylenko + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class CasVersion { + + /** + * Private constructor for CasVersion. You should not be able to instanciate + * this class. + */ + private CasVersion() { + // this class is not instantiable + } + + /** + * 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/CentralAuthenticationService.java b/cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationService.java new file mode 100644 index 0000000..a74c4f3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationService.java @@ -0,0 +1,120 @@ +/* + * 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. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.validation.Assertion; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface CentralAuthenticationService { + + /** + * Create a TicketGrantingTicket based on opaque credentials supplied by the + * caller. + * + * @param credentials The credentials to create the ticket for + * @return The String identifier of the ticket (may not be null). + * @throws TicketException if ticket cannot be created + */ + String createTicketGrantingTicket(Credentials credentials) + throws TicketException; + + /** + * Grant a ServiceTicket for a Service. + * + * @param ticketGrantingTicketId Proof of prior authentication. + * @param service The target service of the ServiceTicket. + * @return the ServiceTicket for target Service. + * @throws TicketException if the ticket could not be created. + */ + String grantServiceTicket(String ticketGrantingTicketId, Service service) + throws TicketException; + + /** + * Grant a ServiceTicket for a Service *if* the principal resolved from the + * credentials matches the principal associated with the + * TicketGrantingTicket. + * + * @param ticketGrantingTicketId Proof of prior authentication. + * @param service The target service of the ServiceTicket. + * @param credentials the Credentials to present to receive the + * ServiceTicket + * @return the ServiceTicket for target Service. + * @throws TicketException if the ticket could not be created. + */ + String grantServiceTicket(final String ticketGrantingTicketId, + final Service service, final Credentials credentials) + throws TicketException; + + /** + * Validate a ServiceTicket for a particular Service. + * + * @param serviceTicketId Proof of prior authentication. + * @param service Service wishing to validate a prior authentication. + * @return ServiceTicket if valid for the service + * @throws TicketException if there was an error validating the ticket. + */ + Assertion validateServiceTicket(final String serviceTicketId, + final Service service) throws TicketException; + + /** + * Destroy a TicketGrantingTicket. This has the effect of invalidating any + * Ticket that was derived from the TicketGrantingTicket being destroyed. + * + * @param ticketGrantingTicketId the id of the ticket we want to destroy + */ + void destroyTicketGrantingTicket(final String ticketGrantingTicketId); + + /** + * Delegate a TicketGrantingTicket to a Service for proxying authentication + * to other Services. + * + * @param serviceTicketId The service ticket that will delegate to a + * TicketGrantingTicket + * @param credentials The credentials of the service that wishes to have a + * TicketGrantingTicket delegated to it. + * @return TicketGrantingTicket that can grant ServiceTickets that proxy + * authentication. + * @throws TicketException if there was an error creating the ticket + */ + String delegateTicketGrantingTicket(final String serviceTicketId, + final Credentials credentials) throws TicketException; +} 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 0000000..9e6342d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/CentralAuthenticationServiceImpl.java @@ -0,0 +1,563 @@ +/* + * 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. + */ +package org.jasig.cas; + +import com.github.inspektr.audit.annotation.Audit; + +import org.apache.commons.lang.StringUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.MutableAuthentication; +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.Credentials; +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.jasig.cas.authentication.principal.SimplePrincipal; +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.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.TicketCreationException; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; +import org.jasig.cas.ticket.TicketValidationException; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ImmutableAssertionImpl; +import org.perf4j.aop.Profiled; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 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: + *

+ * + * @author William G. Thompson, Jr. + * @author Scott Battaglia + * @author Dmitry Kopylenko + * @version $Revision: 1.16 $ $Date: 2007/04/24 18:11:36 $ + * @since 3.0 + */ +public final class CentralAuthenticationServiceImpl implements CentralAuthenticationService { + + /** Log instance for logging events, info, warnings, errors, etc. */ + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + /** TicketRegistry for storing and retrieving tickets as needed. */ + @NotNull + private TicketRegistry ticketRegistry; + + /** New Ticket Registry for storing and retrieving services tickets. Can point to the same one as the ticketRegistry variable. */ + @NotNull + private TicketRegistry serviceTicketRegistry; + + /** + * AuthenticationManager for authenticating credentials for purposes of + * obtaining tickets. + */ + @NotNull + private AuthenticationManager authenticationManager; + + /** + * UniqueTicketIdGenerator to generate ids for TicketGrantingTickets + * created. + */ + @NotNull + private UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator; + + /** Map to contain the mappings of service->UniqueTicketIdGenerators */ + @NotNull + private Map uniqueTicketIdGeneratorsForService; + + /** Expiration policy for ticket granting tickets. */ + @NotNull + private ExpirationPolicy ticketGrantingTicketExpirationPolicy; + + /** ExpirationPolicy for Service Tickets. */ + @NotNull + private ExpirationPolicy serviceTicketExpirationPolicy; + + /** Implementation of Service Manager */ + @NotNull + private ServicesManager servicesManager; + + /** Encoder to generate PseudoIds. */ + @NotNull + private PersistentIdGenerator persistentIdGenerator = new ShibbolethCompatiblePersistentIdGenerator(); + + /** + * Implementation of destoryTicketGrantingTicket expires the ticket provided + * and removes it from the TicketRegistry. + * + * @throws IllegalArgumentException if the TicketGrantingTicket ID is null. + */ + @Audit( + action="TICKET_GRANTING_TICKET_DESTROYED", + actionResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOLVER", + resourceResolverName="DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER") + @Profiled(tag = "DESTROY_TICKET_GRANTING_TICKET",logFailuresSeparately = false) + @Transactional(readOnly = false) + public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) { + Assert.notNull(ticketGrantingTicketId); + + if (log.isDebugEnabled()) { + log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry."); + } + final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class); + + if (ticket == null) { + return; + } + + if (log.isDebugEnabled()) { + log.debug("Ticket found. Expiring and then deleting."); + } + ticket.expire(); + this.ticketRegistry.deleteTicket(ticketGrantingTicketId); + } + + /** + * @throws IllegalArgumentException if TicketGrantingTicket ID, Credentials + * or Service are null. + */ + @Audit( + action="SERVICE_TICKET", + actionResolverName="GRANT_SERVICE_TICKET_RESOLVER", + resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER") + @Profiled(tag="GRANT_SERVICE_TICKET", logFailuresSeparately = false) + @Transactional(readOnly = false) + public String grantServiceTicket(final String ticketGrantingTicketId, final Service service, final Credentials credentials) throws TicketException { + + Assert.notNull(ticketGrantingTicketId, "ticketGrantingticketId cannot be null"); + Assert.notNull(service, "service cannot be null"); + + final TicketGrantingTicket ticketGrantingTicket; + ticketGrantingTicket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class); + + if (ticketGrantingTicket == null) { + throw new InvalidTicketException(); + } + + synchronized (ticketGrantingTicket) { + if (ticketGrantingTicket.isExpired()) { + this.ticketRegistry.deleteTicket(ticketGrantingTicketId); + throw new InvalidTicketException(); + } + } + + final RegisteredService registeredService = this.servicesManager + .findServiceBy(service); + + if (registeredService == null || !registeredService.isEnabled()) { + log.warn("ServiceManagement: Unauthorized Service Access. Service [" + service.getId() + "] not found in Service Registry."); + throw new UnauthorizedServiceException(); + } + + if (!registeredService.isSsoEnabled() && credentials == null + && ticketGrantingTicket.getCountOfUses() > 0) { + log.warn("ServiceManagement: Service Not Allowed to use SSO. Service [" + service.getId() + "]"); + throw new UnauthorizedSsoServiceException(); + } + + //CAS-1019 + final List authns = ticketGrantingTicket.getChainedAuthentications(); + if(authns.size() > 1) { + if (!registeredService.isAllowedToProxy()) { + final String message = String.format("ServiceManagement: Service Attempted to Proxy, but is not allowed. Service: [%s] | Registered Service: [%s]", service.getId(), registeredService.toString()); + log.warn(message); + throw new UnauthorizedProxyingException(message); + } + } + + if (credentials != null) { + try { + final Authentication authentication = this.authenticationManager + .authenticate(credentials); + final Authentication originalAuthentication = ticketGrantingTicket.getAuthentication(); + + if (!(authentication.getPrincipal().equals(originalAuthentication.getPrincipal()) && authentication.getAttributes().equals(originalAuthentication.getAttributes()))) { + throw new TicketCreationException(); + } + } catch (final AuthenticationException e) { + throw new TicketCreationException(e); + } + } + + // this code is a bit brittle by depending on the class name. Future versions (i.e. CAS4 will know inherently how to identify themselves) + final UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator = this.uniqueTicketIdGeneratorsForService + .get(service.getClass().getName()); + + final ServiceTicket serviceTicket = ticketGrantingTicket + .grantServiceTicket(serviceTicketUniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), service, + this.serviceTicketExpirationPolicy, credentials != null); + + this.serviceTicketRegistry.addTicket(serviceTicket); + + if (log.isInfoEnabled()) { + final List authentications = serviceTicket.getGrantingTicket().getChainedAuthentications(); + final String formatString = "Granted %s ticket [%s] for service [%s] for user [%s]"; + final String type; + final String principalId = authentications.get(authentications.size()-1).getPrincipal().getId(); + + if (authentications.size() == 1) { + type = "service"; + + } else { + type = "proxy"; + } + + log.info(String.format(formatString, type, serviceTicket.getId(), service.getId(), principalId)); + } + + return serviceTicket.getId(); + } + + @Audit( + action="SERVICE_TICKET", + actionResolverName="GRANT_SERVICE_TICKET_RESOLVER", + resourceResolverName="GRANT_SERVICE_TICKET_RESOURCE_RESOLVER") + @Profiled(tag = "GRANT_SERVICE_TICKET",logFailuresSeparately = false) + @Transactional(readOnly = false) + public String grantServiceTicket(final String ticketGrantingTicketId, + final Service service) throws TicketException { + return this.grantServiceTicket(ticketGrantingTicketId, service, null); + } + + /** + * @throws IllegalArgumentException if the ServiceTicketId or the + * Credentials are null. + */ + @Audit( + action="PROXY_GRANTING_TICKET", + actionResolverName="GRANT_PROXY_GRANTING_TICKET_RESOLVER", + resourceResolverName="GRANT_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER") + @Profiled(tag="GRANT_PROXY_GRANTING_TICKET",logFailuresSeparately = false) + @Transactional(readOnly = false) + public String delegateTicketGrantingTicket(final String serviceTicketId, + final Credentials credentials) throws TicketException { + + Assert.notNull(serviceTicketId, "serviceTicketId cannot be null"); + Assert.notNull(credentials, "credentials cannot be null"); + + try { + final Authentication authentication = this.authenticationManager + .authenticate(credentials); + + final ServiceTicket serviceTicket; + serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class); + + if (serviceTicket == null || serviceTicket.isExpired()) { + throw new InvalidTicketException(); + } + + final RegisteredService registeredService = this.servicesManager + .findServiceBy(serviceTicket.getService()); + + if (registeredService == null || !registeredService.isEnabled() + || !registeredService.isAllowedToProxy()) { + log.warn("ServiceManagement: Service Attempted to Proxy, but is not allowed. Service: [" + serviceTicket.getService().getId() + "]"); + throw new UnauthorizedProxyingException(); + } + + final TicketGrantingTicket ticketGrantingTicket = serviceTicket + .grantTicketGrantingTicket( + this.ticketGrantingTicketUniqueTicketIdGenerator + .getNewTicketId(TicketGrantingTicket.PREFIX), + authentication, this.ticketGrantingTicketExpirationPolicy); + + this.ticketRegistry.addTicket(ticketGrantingTicket); + + return ticketGrantingTicket.getId(); + } catch (final AuthenticationException e) { + throw new TicketCreationException(e); + } + } + + /** + * @throws IllegalArgumentException if the ServiceTicketId or the Service + * are null. + */ + @Audit( + action="SERVICE_TICKET_VALIDATE", + actionResolverName="VALIDATE_SERVICE_TICKET_RESOLVER", + resourceResolverName="VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER") + @Profiled(tag="VALIDATE_SERVICE_TICKET",logFailuresSeparately = false) + @Transactional(readOnly = false) + public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException { + Assert.notNull(serviceTicketId, "serviceTicketId cannot be null"); + Assert.notNull(service, "service cannot be null"); + + final ServiceTicket serviceTicket = (ServiceTicket) this.serviceTicketRegistry.getTicket(serviceTicketId, ServiceTicket.class); + + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + + if (registeredService == null || !registeredService.isEnabled()) { + log.warn("ServiceManagement: Service does not exist is not enabled, and thus not allowed to validate tickets. Service: [" + service.getId() + "]"); + throw new UnauthorizedServiceException("Service not allowed to validate tickets."); + } + + if (serviceTicket == null) { + log.info("ServiceTicket [" + serviceTicketId + "] does not exist."); + throw new InvalidTicketException(); + } + + try { + synchronized (serviceTicket) { + if (serviceTicket.isExpired()) { + log.info("ServiceTicket [" + serviceTicketId + "] has expired."); + throw new InvalidTicketException(); + } + + if (!serviceTicket.isValidFor(service)) { + log.error("ServiceTicket [" + serviceTicketId + "] with service [" + serviceTicket.getService().getId() + " does not match supplied service [" + service + "]"); + throw new TicketValidationException(serviceTicket.getService()); + } + } + + final List chainedAuthenticationsList = serviceTicket.getGrantingTicket().getChainedAuthentications(); + final Authentication authentication = chainedAuthenticationsList.get(chainedAuthenticationsList.size() - 1); + final Principal principal = authentication.getPrincipal(); + + final String principalId = determinePrincipalIdForRegisteredService(principal, registeredService, serviceTicket); + final Authentication authToUse; + + if (!registeredService.isIgnoreAttributes()) { + final Map attributes = new HashMap(); + + for (final String attribute : registeredService.getAllowedAttributes()) { + final Object value = principal.getAttributes().get(attribute); + + if (value != null) { + attributes.put(attribute, value); + } + } + + final Principal modifiedPrincipal = new SimplePrincipal(principalId, attributes); + final MutableAuthentication mutableAuthentication = new MutableAuthentication( + modifiedPrincipal, authentication.getAuthenticatedDate()); + mutableAuthentication.getAttributes().putAll( + authentication.getAttributes()); + mutableAuthentication.getAuthenticatedDate().setTime( + authentication.getAuthenticatedDate().getTime()); + authToUse = mutableAuthentication; + } else { + final Principal modifiedPrincipal = new SimplePrincipal(principalId, principal.getAttributes()); + authToUse = new MutableAuthentication(modifiedPrincipal, authentication.getAuthenticatedDate()); + } + + final List authentications = new ArrayList(); + + for (int i = 0; i < chainedAuthenticationsList.size() - 1; i++) { + authentications.add(serviceTicket.getGrantingTicket().getChainedAuthentications().get(i)); + } + authentications.add(authToUse); + + return new ImmutableAssertionImpl(authentications, serviceTicket.getService(), serviceTicket.isFromNewLogin()); + } finally { + if (serviceTicket.isExpired()) { + this.serviceTicketRegistry.deleteTicket(serviceTicketId); + } + } + } + + /** + * Determines the principal id to use for a {@link RegisteredService} using the following rules: + * + *
    + *
  • If the service is marked to allow anonymous access, a persistent id is returned.
  • + *
  • If the attribute name matches {@link RegisteredService#DEFAULT_USERNAME_ATTRIBUTE}, then the default principal id is returned.
  • + *
  • If the service is set to ignore attributes, or the username attribute exists in the allowed attributes for the service, + * the corresponding attribute value will be returned. + *
  • + *
  • Otherwise, the default principal's id is returned as the username attribute with an additional warning.
  • + *
+ * + * @param principal The principal object to be validated and constructed + * @param registeredService Requesting service for which a principal is being validated. + * @param serviceTicket An instance of the service ticket used for validation + * + * @return The principal id to use for the requesting registered service + */ + private String determinePrincipalIdForRegisteredService(final Principal principal, final RegisteredService registeredService, + final ServiceTicket serviceTicket) { + String principalId = null; + final String serviceUsernameAttribute = registeredService.getUsernameAttribute(); + + if (registeredService.isAnonymousAccess()) { + principalId = this.persistentIdGenerator.generate(principal, serviceTicket.getService()); + } else if (StringUtils.isBlank(serviceUsernameAttribute)) { + principalId = principal.getId(); + } else { + if ((registeredService.isIgnoreAttributes() || registeredService.getAllowedAttributes().contains(serviceUsernameAttribute)) && + principal.getAttributes().containsKey(serviceUsernameAttribute)) { + principalId = principal.getAttributes().get(registeredService.getUsernameAttribute()).toString(); + } else { + principalId = principal.getId(); + final Object[] errorLogParameters = new Object[] { principalId, registeredService.getUsernameAttribute(), + principal.getAttributes(), registeredService.getServiceId(), principalId }; + log.warn("Principal [{}] did not have attribute [{}] among attributes [{}] so CAS cannot " + + "provide on the validation response the user attribute the registered service [{}] expects. " + + "CAS will instead return the default username attribute [{}]", errorLogParameters); + } + + } + + log.debug("Principal id to return for service [{}] is [{}]. The default principal id is [{}].", + new Object[] {registeredService.getName(), principal.getId(), principalId}); + return principalId; + } + + /** + * @throws IllegalArgumentException if the credentials are null. + */ + @Audit( + action="TICKET_GRANTING_TICKET", + actionResolverName="CREATE_TICKET_GRANTING_TICKET_RESOLVER", + resourceResolverName="CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER") + @Profiled(tag = "CREATE_TICKET_GRANTING_TICKET", logFailuresSeparately = false) + @Transactional(readOnly = false) + public String createTicketGrantingTicket(final Credentials credentials) throws TicketCreationException { + Assert.notNull(credentials, "credentials cannot be null"); + + try { + 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.getId(); + } catch (final AuthenticationException e) { + throw new TicketCreationException(e); + } + } + + /** + * Method to set the TicketRegistry. + * + * @param ticketRegistry the TicketRegistry to set. + */ + public void setTicketRegistry(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + + if (this.serviceTicketRegistry == null) { + this.serviceTicketRegistry = ticketRegistry; + } + } + + public void setServiceTicketRegistry(final TicketRegistry serviceTicketRegistry) { + this.serviceTicketRegistry = serviceTicketRegistry; + } + + /** + * Method to inject the AuthenticationManager into the class. + * + * @param authenticationManager The authenticationManager to set. + */ + public void setAuthenticationManager( + final AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + /** + * Method to inject the TicketGrantingTicket Expiration Policy. + * + * @param ticketGrantingTicketExpirationPolicy The + * ticketGrantingTicketExpirationPolicy to set. + */ + public void setTicketGrantingTicketExpirationPolicy( + final ExpirationPolicy ticketGrantingTicketExpirationPolicy) { + this.ticketGrantingTicketExpirationPolicy = ticketGrantingTicketExpirationPolicy; + } + + /** + * Method to inject the Unique Ticket Id Generator into the class. + * + * @param uniqueTicketIdGenerator The uniqueTicketIdGenerator to use + */ + public void setTicketGrantingTicketUniqueTicketIdGenerator( + final UniqueTicketIdGenerator uniqueTicketIdGenerator) { + this.ticketGrantingTicketUniqueTicketIdGenerator = uniqueTicketIdGenerator; + } + + /** + * Method to inject the TicketGrantingTicket Expiration Policy. + * + * @param serviceTicketExpirationPolicy The serviceTicketExpirationPolicy to + * set. + */ + public void setServiceTicketExpirationPolicy( + final ExpirationPolicy serviceTicketExpirationPolicy) { + this.serviceTicketExpirationPolicy = serviceTicketExpirationPolicy; + } + + public void setUniqueTicketIdGeneratorsForService( + final Map uniqueTicketIdGeneratorsForService) { + this.uniqueTicketIdGeneratorsForService = uniqueTicketIdGeneratorsForService; + } + + public void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } + + public void setPersistentIdGenerator( + final PersistentIdGenerator persistentIdGenerator) { + this.persistentIdGenerator = persistentIdGenerator; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/aspect/LogAspect.java b/cas-server-core/src/main/java/org/jasig/cas/aspect/LogAspect.java new file mode 100644 index 0000000..aaa6b24 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/aspect/LogAspect.java @@ -0,0 +1,76 @@ +/* + * 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. + */ +package org.jasig.cas.aspect; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + + +/** + * + * + * @version $Revision$ $Date$ + * @since 3.3.6 + */ +@Aspect +public class LogAspect { + + @Around("(execution (public * org.jasig.cas..*.*(..))) && !(execution( * org.jasig.cas..*.set*(..)))") + public Object traceMethod(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + Object returnVal = null; + final Logger log = getLog(proceedingJoinPoint); + final String methodName = proceedingJoinPoint.getSignature().getName(); + + try { + if (log.isTraceEnabled()) { + final Object[] args = proceedingJoinPoint.getArgs(); + final String arguments; + if (args == null || args.length == 0) { + arguments = ""; + } else { + arguments = Arrays.deepToString(args); + } + log.trace("Entering method [" + methodName + " with arguments [" + arguments + "]"); + } + returnVal = proceedingJoinPoint.proceed(); + return returnVal; + } finally { + if (log.isTraceEnabled()) { + log.trace("Leaving method [" + methodName + "] with return value [" + (returnVal != null ? returnVal.toString() : "null") + "]."); + } + } + } + + protected Logger getLog(final JoinPoint joinPoint) { + final Object target = joinPoint.getTarget(); + + if (target != null) { + return LoggerFactory.getLogger(target.getClass()); + } + + return LoggerFactory.getLogger(getClass()); + } + +} 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 0000000..4bcaf10 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/CredentialsAsFirstParameterResourceResolver.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +package org.jasig.cas.audit.spi; + +import org.aspectj.lang.JoinPoint; +import com.github.inspektr.audit.spi.AuditResourceResolver; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.util.AopUtils; + +/** + * Converts the Credentials object into a String resource identifier. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1.2 + * + */ +public final class CredentialsAsFirstParameterResourceResolver implements AuditResourceResolver { + + public String[] resolveFrom(final JoinPoint joinPoint, final Object retval) { + final Credentials credentials = (Credentials) AopUtils.unWrapJoinPoint(joinPoint).getArgs()[0]; + return new String[] { "supplied credentials: " + credentials.toString() }; + } + + public String[] resolveFrom(final JoinPoint joinPoint, final Exception exception) { + final Credentials credentials = (Credentials) AopUtils.unWrapJoinPoint(joinPoint).getArgs()[0]; + return new String[] { "supplied credentials: " + credentials.toString() }; + } +} 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 0000000..467c3f6 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceManagementResourceResolver.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +package org.jasig.cas.audit.spi; + +import com.github.inspektr.audit.spi.AuditResourceResolver; +import org.aspectj.lang.JoinPoint; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.util.AopUtils; + +import javax.validation.constraints.NotNull; + +/** + * Resolves a service id to the service. + *

+ * The expectation is that args[0] is a Long. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.4.6 + */ +public final class ServiceManagementResourceResolver implements AuditResourceResolver { + + public String[] resolveFrom(final JoinPoint target, final Object returnValue) { + return findService(target); + } + + public String[] resolveFrom(final JoinPoint target, final Exception exception) { + return findService(target); + } + + 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 0000000..fcd1e7b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/ServiceResourceResolver.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +package org.jasig.cas.audit.spi; + +import org.aspectj.lang.JoinPoint; +import com.github.inspektr.audit.spi.AuditResourceResolver; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.util.AopUtils; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1.2 + * + */ +public final class ServiceResourceResolver implements AuditResourceResolver { + + public String[] resolveFrom(final JoinPoint joinPoint, final Object retval) { + final Service service = (Service) AopUtils.unWrapJoinPoint(joinPoint).getArgs()[1]; + return new String[] { retval.toString() + " for " + service.getId() }; + } + + 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 0000000..f81c77b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketAsFirstParameterResourceResolver.java @@ -0,0 +1,42 @@ +/* + * 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. + */ +package org.jasig.cas.audit.spi; + +import com.github.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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1.2 + * + */ +public final class TicketAsFirstParameterResourceResolver implements AuditResourceResolver { + + public String[] resolveFrom(final JoinPoint joinPoint, final Exception exception) { + return new String[] { AopUtils.unWrapJoinPoint(joinPoint).getArgs()[0].toString() }; + } + + 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 0000000..878a052 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/audit/spi/TicketOrCredentialPrincipalResolver.java @@ -0,0 +1,90 @@ +/* + * 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. + */ +package org.jasig.cas.audit.spi; + +import org.aspectj.lang.JoinPoint; +import com.github.inspektr.common.spi.PrincipalResolver; +import org.jasig.cas.authentication.principal.Credentials; +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.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 Credentials. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1.2 + * + */ +public final class TicketOrCredentialPrincipalResolver implements PrincipalResolver { + + @NotNull + private final TicketRegistry ticketRegistry; + + public TicketOrCredentialPrincipalResolver(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + } + + public String resolveFrom(final JoinPoint joinPoint, final Object retVal) { + return resolveFromInternal(AopUtils.unWrapJoinPoint(joinPoint)); + } + + public String resolveFrom(final JoinPoint joinPoint, final Exception retVal) { + return resolveFromInternal(AopUtils.unWrapJoinPoint(joinPoint)); + } + + public String resolve() { + return UNKNOWN_USER; + } + + protected String resolveFromInternal(final JoinPoint joinPoint) { + final Object arg1 = joinPoint.getArgs()[0]; + if (arg1 instanceof Credentials) { + return arg1.toString(); + } else if (arg1 instanceof String) { + final Ticket ticket = this.ticketRegistry.getTicket((String) arg1); + 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(); + } + } else { + final SecurityContext securityContext = SecurityContextHolder.getContext(); + if (securityContext != null) { + final Authentication authentication = securityContext.getAuthentication(); + + if (authentication != null) { + return ((UserDetails) authentication.getPrincipal()).getUsername(); + } + } + } + return UNKNOWN_USER; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthentication.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthentication.java new file mode 100644 index 0000000..bd47b4c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthentication.java @@ -0,0 +1,77 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Map; + +import org.jasig.cas.authentication.principal.Principal; +import org.springframework.util.Assert; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date: 2007-02-20 09:41:49 -0500 (Tue, 20 Feb + * 2007) $ + * @since 3.0.3 + */ +public abstract class AbstractAuthentication implements Authentication { + + /** A Principal object representing the authenticated entity. */ + private final Principal principal; + + /** Associated authentication attributes. */ + private final Map attributes; + + public AbstractAuthentication(final Principal principal, + final Map attributes) { + Assert.notNull(principal, "principal cannot be null"); + Assert.notNull(attributes, "attributes cannot be null"); + + this.principal = principal; + this.attributes = attributes; + } + + public final Map getAttributes() { + return this.attributes; + } + + public final Principal getPrincipal() { + return this.principal; + } + + public final boolean equals(final Object o) { + if (o == null || !this.getClass().isAssignableFrom(o.getClass())) { + return false; + } + + Authentication a = (Authentication) o; + + return this.principal.equals(a.getPrincipal()) + && this.getAuthenticatedDate().equals(a.getAuthenticatedDate()) && this.attributes.equals(a.getAttributes()); + } + + public final int hashCode() { + return 49 * this.principal.hashCode() + ^ this.getAuthenticatedDate().hashCode(); + } + + public final String toString() { + return "[Principal=" + this.principal.getId() + ", attributes=" + + this.attributes.toString() + "]"; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthenticationManager.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthenticationManager.java new file mode 100644 index 0000000..2c43e24 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AbstractAuthenticationManager.java @@ -0,0 +1,140 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.ArrayList; +import java.util.List; +import javax.validation.constraints.NotNull; + +import com.github.inspektr.audit.annotation.Audit; +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.AuthenticationHandler; +import org.jasig.cas.authentication.handler.NamedAuthenticationHandler; +import org.jasig.cas.authentication.handler.UncategorizedAuthenticationException; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.Principal; +import org.perf4j.aop.Profiled; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.3.5 + */ +public abstract class AbstractAuthenticationManager implements AuthenticationManager { + + /** Log instance for logging events, errors, warnings, etc. */ + protected final Logger log = LoggerFactory.getLogger(AuthenticationManagerImpl.class); + + /** An array of AuthenticationAttributesPopulators. */ + @NotNull + private List authenticationMetaDataPopulators = new ArrayList(); + + @Audit( + action="AUTHENTICATION", + actionResolverName="AUTHENTICATION_RESOLVER", + resourceResolverName="AUTHENTICATION_RESOURCE_RESOLVER") + @Profiled(tag = "AUTHENTICATE", logFailuresSeparately = false) + public final Authentication authenticate(final Credentials credentials) throws AuthenticationException { + + final Pair pair = authenticateAndObtainPrincipal(credentials); + + // we can only get here if the above method doesn't throw an exception. And if it doesn't, then the pair must not be null. + final Principal p = pair.getSecond(); + log.info("{} authenticated {} with credential {}.", pair.getFirst(), p, credentials); + log.debug("Attribute map for {}: {}", p.getId(), p.getAttributes()); + + Authentication authentication = new MutableAuthentication(p); + + if (pair.getFirst()instanceof NamedAuthenticationHandler) { + final NamedAuthenticationHandler a = (NamedAuthenticationHandler) pair.getFirst(); + authentication.getAttributes().put(AuthenticationManager.AUTHENTICATION_METHOD_ATTRIBUTE, a.getName()); + } + + for (final AuthenticationMetaDataPopulator authenticationMetaDataPopulator : this.authenticationMetaDataPopulators) { + authentication = authenticationMetaDataPopulator + .populateAttributes(authentication, credentials); + } + + return new ImmutableAuthentication(authentication.getPrincipal(), + authentication.getAttributes()); + } + + /** + * @param authenticationMetaDataPopulators the authenticationMetaDataPopulators to set. + */ + public final void setAuthenticationMetaDataPopulators(final List authenticationMetaDataPopulators) { + this.authenticationMetaDataPopulators = authenticationMetaDataPopulators; + } + + /** + * Follows the same rules as the "authenticate" method (i.e. should only return a fully populated object, or throw an exception) + * + * @param credentials the credentials to check + * @return the pair of authentication handler and principal. CANNOT be NULL. + * @throws AuthenticationException if there is an error authenticating. + */ + protected abstract Pair authenticateAndObtainPrincipal(Credentials credentials) throws AuthenticationException; + + + /** + * Handles an authentication error raised by an {@link AuthenticationHandler}. + * + * @param handlerName The class name of the authentication handler. + * @param credentials Client credentials subject to authentication. + * @param e The exception that has occurred during authentication attempt. + */ + protected void handleError(final String handlerName, final Credentials credentials, final Exception e) + throws AuthenticationException { + if (e instanceof AuthenticationException) { + // CAS-1181 Log common authentication failures at INFO without stack trace + log.info("{} failed authenticating {}", handlerName, credentials); + throw (AuthenticationException) e; + } + log.error("{} threw error authenticating {}", handlerName, credentials, e); + throw new UncategorizedAuthenticationException(e.getClass().getName(), e) { + // Anonymous inner class allows us to throw uncategorized authentication error + // since base class is abstract. + }; + } + + + protected static class Pair { + + private final A first; + + private final B second; + + public Pair(final A first, final B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { + return this.first; + } + + + public B getSecond() { + return this.second; + } + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/Authentication.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/Authentication.java new file mode 100644 index 0000000..43bfa3c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/Authentication.java @@ -0,0 +1,74 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.io.Serializable; +import java.util.Date; +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 + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +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 getAuthenticatedDate(); + + /** + * Attributes of the authentication (not the Principal). + * + * @return the map of attributes. + */ + Map getAttributes(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationManager.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationManager.java new file mode 100644 index 0000000..56c7b95 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationManager.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.Credentials; + +/** + * The AuthenticationManager class is the entity that determines the + * authenticity of the credentials provided. It (or a class it delegates to) is + * the sole authority on whether credentials are valid or not. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface AuthenticationManager { + + String AUTHENTICATION_METHOD_ATTRIBUTE = "authenticationMethod"; + + /** + * Method to validate the credentials provided. On successful validation, a + * fully populated Authentication object will be returned. Typically this + * will involve resolving a principal and providing any additional + * attributes, but specifics are left to the individual implementations to + * determine. Failure to authenticate is considered an exceptional case, and + * an AuthenticationException is thrown. + * + * @param credentials The credentials provided for authentication. + * @return fully populated Authentication object. + * @throws AuthenticationException if unable to determine validity of + * credentials or there is an extenuating circumstance related to + * credentials (i.e. Account locked). + */ + Authentication authenticate(final Credentials credentials) + throws AuthenticationException; +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationManagerImpl.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationManagerImpl.java new file mode 100644 index 0000000..e6c2ce5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationManagerImpl.java @@ -0,0 +1,155 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.List; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.AuthenticationHandler; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; +import org.jasig.cas.authentication.handler.UnsupportedCredentialsException; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver; +import org.jasig.cas.authentication.principal.Principal; + +/** + *

+ * Default implementation of the AuthenticationManager. The + * AuthenticationManager follows the following algorithm. The manager loops + * through the array of AuthenticationHandlers searching for one that can + * attempt to determine the validity of the credentials. If it finds one, it + * tries that one. If that handler returns true, it continues on. If it returns + * false, it looks for another handler. If it throws an exception, it aborts the + * whole process and rethrows the exception. Next, it looks for a + * CredentialsToPrincipalResolver that can handle the credentials in order to + * create a Principal. Finally, it attempts to populate the Authentication + * object's attributes map using AuthenticationAttributesPopulators + *

+ * Behavior is determined by external beans attached through three configuration + * properties. The Credentials are opaque to the manager. They are passed to the + * external beans to see if any can process the actual type represented by the + * Credentials marker. + *

+ * AuthenticationManagerImpl requires the following properties to be set: + *

+ *
    + *
  • authenticationHandlers - The array of + * AuthenticationHandlers that know how to process the credentials provided. + *
  • credentialsToPrincipalResolvers - The array of + * CredentialsToPrincipal resolvers that know how to process the credentials + * provided. + *
+ * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + * @see org.jasig.cas.authentication.handler.AuthenticationHandler + * @see org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver + * @see org.jasig.cas.authentication.AuthenticationMetaDataPopulator + */ + +public final class AuthenticationManagerImpl extends AbstractAuthenticationManager { + + /** An array of authentication handlers. */ + @NotNull + @Size(min=1) + private List authenticationHandlers; + + /** An array of CredentialsToPrincipalResolvers. */ + @NotNull + @Size(min=1) + private List credentialsToPrincipalResolvers; + + @Override + protected Pair authenticateAndObtainPrincipal(final Credentials credentials) throws AuthenticationException { + boolean foundSupported = false; + boolean authenticated = false; + AuthenticationHandler authenticatedClass = null; + String handlerName; + + for (final AuthenticationHandler authenticationHandler : this.authenticationHandlers) { + if (authenticationHandler.supports(credentials)) { + foundSupported = true; + handlerName = authenticationHandler.getClass().getName(); + try { + if (!authenticationHandler.authenticate(credentials)) { + log.info("{} failed to authenticate {}", handlerName, credentials); + } else { + log.info("{} successfully authenticated {}", handlerName, credentials); + authenticatedClass = authenticationHandler; + authenticated = true; + break; + } + } catch (final Exception e) { + handleError(handlerName, credentials, e); + } + } + } + + if (!authenticated) { + if (foundSupported) { + throw BadCredentialsAuthenticationException.ERROR; + } + throw UnsupportedCredentialsException.ERROR; + } + + foundSupported = false; + + for (final CredentialsToPrincipalResolver credentialsToPrincipalResolver : this.credentialsToPrincipalResolvers) { + if (credentialsToPrincipalResolver.supports(credentials)) { + final Principal principal = credentialsToPrincipalResolver.resolvePrincipal(credentials); + log.info("Resolved principal " + principal); + foundSupported = true; + if (principal != null) { + return new Pair(authenticatedClass, principal); + } + } + } + + if (foundSupported) { + if (log.isDebugEnabled()) { + log.debug("CredentialsToPrincipalResolver found but no principal returned."); + } + + throw BadCredentialsAuthenticationException.ERROR; + } + + log.error("CredentialsToPrincipalResolver not found for " + credentials.getClass().getName()); + throw UnsupportedCredentialsException.ERROR; + } + + /** + * @param authenticationHandlers The authenticationHandlers to set. + */ + public void setAuthenticationHandlers( + final List authenticationHandlers) { + this.authenticationHandlers = authenticationHandlers; + } + + /** + * @param credentialsToPrincipalResolvers The + * credentialsToPrincipalResolvers to set. + */ + public void setCredentialsToPrincipalResolvers( + final List credentialsToPrincipalResolvers) { + this.credentialsToPrincipalResolvers = credentialsToPrincipalResolvers; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationMetaDataPopulator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationMetaDataPopulator.java new file mode 100644 index 0000000..f8ad70c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/AuthenticationMetaDataPopulator.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * 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. AuthenticationAttributePopulators are a new feature in + * CAS3. In order for an installation to be CAS2 compliant, deployers do not + * need an AuthenticationMetaDataPopulator. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface AuthenticationMetaDataPopulator { + + /** + * Provided with an Authentication object and the original credentials + * presented, provide any additional attributes to the Authentication + * object. Implementations have the option of returning the same + * Authentication object, or a new one. + * + * @param authentication The Authentication to potentially augment with + * additional attributes. + * @return the original Authentication object or a new Authentication + * object. + */ + Authentication populateAttributes(Authentication authentication, + Credentials credentials); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/DirectMappingAuthenticationManagerImpl.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/DirectMappingAuthenticationManagerImpl.java new file mode 100644 index 0000000..f2bb849 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/DirectMappingAuthenticationManagerImpl.java @@ -0,0 +1,112 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Map; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.AuthenticationHandler; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver; +import org.jasig.cas.authentication.principal.Principal; +import org.springframework.util.Assert; + +/** + * Authentication Manager that provides a direct mapping between credentials + * provided and the authentication handler used to authenticate the user. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class DirectMappingAuthenticationManagerImpl extends AbstractAuthenticationManager { + + @NotNull + @Size(min=1) + private Map, DirectAuthenticationHandlerMappingHolder> credentialsMapping; + + /** + * @throws IllegalArgumentException if a mapping cannot be found. + * @see org.jasig.cas.authentication.AuthenticationManager#authenticate(org.jasig.cas.authentication.principal.Credentials) + */ + @Override + protected Pair authenticateAndObtainPrincipal(final Credentials credentials) throws AuthenticationException { + final Class< ? extends Credentials> credentialsClass = credentials.getClass(); + final DirectAuthenticationHandlerMappingHolder d = this.credentialsMapping.get(credentialsClass); + + Assert.notNull(d, "no mapping found for: " + credentialsClass.getName()); + + final String handlerName = d.getAuthenticationHandler().getClass().getSimpleName(); + boolean authenticated = false; + + try { + authenticated = d.getAuthenticationHandler().authenticate(credentials); + } catch (final Exception e) { + handleError(handlerName, credentials, e); + } + + if (!authenticated) { + log.info("{} failed to authenticate {}", handlerName, credentials); + throw BadCredentialsAuthenticationException.ERROR; + } + log.info("{} successfully authenticated {}", handlerName, credentials); + + final Principal p = d.getCredentialsToPrincipalResolver().resolvePrincipal(credentials); + + return new Pair(d.getAuthenticationHandler(), p); + } + + public final void setCredentialsMapping( + final Map, DirectAuthenticationHandlerMappingHolder> credentialsMapping) { + this.credentialsMapping = credentialsMapping; + } + + public static final class DirectAuthenticationHandlerMappingHolder { + + private AuthenticationHandler authenticationHandler; + + private CredentialsToPrincipalResolver credentialsToPrincipalResolver; + + public DirectAuthenticationHandlerMappingHolder() { + // nothing to do + } + + public final AuthenticationHandler getAuthenticationHandler() { + return this.authenticationHandler; + } + + public void setAuthenticationHandler( + final AuthenticationHandler authenticationHandler) { + this.authenticationHandler = authenticationHandler; + } + + public CredentialsToPrincipalResolver getCredentialsToPrincipalResolver() { + return this.credentialsToPrincipalResolver; + } + + public void setCredentialsToPrincipalResolver( + final CredentialsToPrincipalResolver credentialsToPrincipalResolver) { + this.credentialsToPrincipalResolver = credentialsToPrincipalResolver; + } + } + +} 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 0000000..a7a1bde --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/ImmutableAuthentication.java @@ -0,0 +1,78 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.authentication.principal.Principal; + +/** + * Default implementation of Authentication interface. ImmutableAuthentication + * is an immutable object and thus its attributes cannot be changed. + *

+ * Instanciators of the ImmutableAuthentication class must take care that the + * map they provide is serializable (i.e. HashMap). + * + * @author Dmitriy Kopylenko + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class ImmutableAuthentication extends AbstractAuthentication { + + /** UID for serializing. */ + private static final long serialVersionUID = 3906647483978365235L; + + private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap()); + + /** The date/time this authentication object became valid. */ + final Date authenticatedDate; + + /** + * Constructor that accepts both a principal and a map. + * + * @param principal Principal representing user + * @param attributes Authentication attributes map. + * @throws IllegalArgumentException if the principal is null. + */ + public ImmutableAuthentication(final Principal principal, + final Map attributes) { + super(principal, attributes == null || attributes.isEmpty() + ? EMPTY_MAP : Collections.unmodifiableMap(attributes)); + + this.authenticatedDate = new Date(); + } + + /** + * Constructor that assumes there are no additional authentication + * attributes. + * + * @param principal the Principal representing the authenticated entity. + */ + public ImmutableAuthentication(final Principal principal) { + this(principal, null); + } + + public Date getAuthenticatedDate() { + return new Date(this.authenticatedDate.getTime()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/LinkedAuthenticationHandlerAndCredentialsToPrincipalResolverAuthenticationManager.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/LinkedAuthenticationHandlerAndCredentialsToPrincipalResolverAuthenticationManager.java new file mode 100644 index 0000000..f9abe75 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/LinkedAuthenticationHandlerAndCredentialsToPrincipalResolverAuthenticationManager.java @@ -0,0 +1,84 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Map; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.AuthenticationHandler; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; +import org.jasig.cas.authentication.handler.UnsupportedCredentialsException; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver; +import org.jasig.cas.authentication.principal.Principal; + +/** + * Ensures that all authentication handlers are tried, but if one is tried, the associated CredentialsToPrincipalResolver is used. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.3.5 + */ +public class LinkedAuthenticationHandlerAndCredentialsToPrincipalResolverAuthenticationManager extends AbstractAuthenticationManager { + + @NotNull + @Size(min = 1) + private final Map linkedHandlers; + + public LinkedAuthenticationHandlerAndCredentialsToPrincipalResolverAuthenticationManager(final Map linkedHandlers) { + this.linkedHandlers = linkedHandlers; + } + + @Override + protected Pair authenticateAndObtainPrincipal(final Credentials credentials) throws AuthenticationException { + boolean foundOneThatWorks = false; + String handlerName; + + for (final AuthenticationHandler authenticationHandler : this.linkedHandlers.keySet()) { + if (!authenticationHandler.supports(credentials)) { + continue; + } + + foundOneThatWorks = true; + boolean authenticated = false; + handlerName = authenticationHandler.getClass().getName(); + + try { + authenticated = authenticationHandler.authenticate(credentials); + } catch (final Exception e) { + handleError(handlerName, credentials, e); + } + + if (authenticated) { + log.info("{} successfully authenticated {}", handlerName, credentials); + final Principal p = this.linkedHandlers.get(authenticationHandler).resolvePrincipal(credentials); + return new Pair(authenticationHandler, p); + } + log.info("{} failed to authenticate {}", handlerName, credentials); + } + + if (foundOneThatWorks) { + throw BadCredentialsAuthenticationException.ERROR; + } + + throw UnsupportedCredentialsException.ERROR; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/MutableAuthentication.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/MutableAuthentication.java new file mode 100644 index 0000000..02fe74a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/MutableAuthentication.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Date; +import java.util.HashMap; + +import org.jasig.cas.authentication.principal.Principal; + +/** + * Mutable implementation of Authentication interface. + *

+ * Instanciators of the MutableAuthentication class must take care that the map + * they provide is serializable (i.e. HashMap). + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.3 + */ +public final class MutableAuthentication extends AbstractAuthentication { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -4415875344376642246L; + + /** The date/time this authentication object became valid. */ + private final Date authenticatedDate; + + public MutableAuthentication(final Principal principal) { + this(principal, new Date()); + } + + public MutableAuthentication(final Principal principal, final Date date) { + super(principal, new HashMap()); + this.authenticatedDate = date; + } + + public Date getAuthenticatedDate() { + return this.authenticatedDate; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/SamlAuthenticationMetaDataPopulator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/SamlAuthenticationMetaDataPopulator.java new file mode 100644 index 0000000..fba5a9a --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/SamlAuthenticationMetaDataPopulator.java @@ -0,0 +1,94 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.HttpBasedServiceCredentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; + +/** + * AuthenticationMetaDataPopulator to retrieve the Authentication Type. + *

+ * Note: Authentication Methods are exposed under the key: + * samlAuthenticationStatement::authMethod in the Authentication + * attributes map. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class SamlAuthenticationMetaDataPopulator implements + AuthenticationMetaDataPopulator { + + public static final String ATTRIBUTE_AUTHENTICATION_METHOD = "samlAuthenticationStatementAuthMethod"; + + public static final String AUTHN_METHOD_PASSWORD = "urn:oasis:names:tc:SAML:1.0:am:password"; + + public static final String AUTHN_METHOD_SSL_TLS_CLIENT = "urn:ietf:rfc:2246"; + + public static final String AUTHN_METHOD_X509_PUBLICKEY = "urn:oasis:names:tc:SAML:1.0:am:X509-PKI"; + + public static final String AUTHN_METHOD_UNSPECIFIED = "urn:oasis:names:tc:SAML:1.0:am:unspecified"; + + private final Map authenticationMethods = new HashMap(); + + public SamlAuthenticationMetaDataPopulator() { + this.authenticationMethods.put( + HttpBasedServiceCredentials.class.getName(), + AUTHN_METHOD_SSL_TLS_CLIENT); + this.authenticationMethods.put( + UsernamePasswordCredentials.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); + } + + public final Authentication populateAttributes(final Authentication authentication, final Credentials credentials) { + + final String credentialsClass = credentials.getClass().getName(); + final String authenticationMethod = this.authenticationMethods.get(credentialsClass); + + authentication.getAttributes().put(ATTRIBUTE_AUTHENTICATION_METHOD, authenticationMethod); + + return authentication; + } + + /** + * Map of user-defined mappings. Note it is possible to over-ride the + * defaults. Mapping should be of the following type: + *

( ) + *

+ * Example: (<"org.jasig.cas.authentication.principal.HttpBasedServiceCredentials"> + * ) + * + * @param userDefinedMappings map of user defined authentication types. + */ + public void setUserDefinedMappings(final Map userDefinedMappings) { + this.authenticationMethods.putAll(userDefinedMappings); + } +} 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 0000000..e39a6b6 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationException.java @@ -0,0 +1,120 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public abstract class AuthenticationException extends Exception { + + /** Serializable ID. */ + private static final long serialVersionUID = 3906648604830611762L; + + /** The code to return for resolving to a message description. */ + private String code; + + /** The error type that provides additional info about the nature of the exception cause **/ + private String type = "error"; + + /** + * Constructor that takes a code description of the error. These codes + * normally have a corresponding entries in the messages file for the + * internationalization of error messages. + * + * @param code The short unique identifier for this error. + */ + public AuthenticationException(final String code) { + this.code = 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 short unique identifier for this error. + * @param msg The error message associated with this exception for additional logging purposes. + */ + public AuthenticationException(final String code, final String msg) { + super(msg); + this.code = code; + } + + /** + * Constructor that takes a code description of the error along with the exception + * msg generally for logging purposes and the type of the error that originally caused the exception. + * These codes normally have a corresponding entries in the messages file for the internationalization of error messages. + * + * @param code The short unique identifier for this error. + * @param msg The error message associated with this exception for additional logging purposes. + * @param type The type of the error message that caused the exception to be thrown. By default, + * all errors are considered of error. + */ + public AuthenticationException(final String code, final String msg, final String type) { + super(msg); + this.code = code; + this.type = type; + } + + /** + * Constructor that takes a code description of the error and the chained + * exception. These codes normally have a corresponding entries in the + * messages file for the internationalization of error messages. + * + * @param code The short unique identifier for this error. + * @param throwable The chained exception for this AuthenticationException + */ + public AuthenticationException(final String code, final Throwable throwable) { + super(throwable); + this.code = code; + } + + /** + * 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; + } + + /** + * Method to return the unique identifier for this error type. + * + * @return the String identifier for this error type. + */ + public final String getCode() { + return this.code; + } + + @Override + public final String toString() { + String msg = getCode(); + if (getMessage() != null && getMessage().trim().length() > 0) + msg = ":" + getMessage(); + return msg; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationHandler.java new file mode 100644 index 0000000..5769d92 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/AuthenticationHandler.java @@ -0,0 +1,64 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * Validate Credentials support for AuthenticationManagerImpl. + *

+ * Determines that Credentials are valid. Password-based credentials 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 credentials provided. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface AuthenticationHandler { + + /** + * Method to determine if the credentials supplied are valid. + * + * @param credentials The credentials 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 credentials) + throws AuthenticationException; + + /** + * Method to check if the handler knows how to handle the credentials + * provided. It may be a simple check of the Credentials class or something + * more complicated such as scanning the information contained in the + * Credentials object. + * + * @param credentials The credentials to check. + * @return true if the handler supports the Credentials, false othewrise. + */ + boolean supports(Credentials credentials); +} 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 0000000..4fff9d9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationException.java @@ -0,0 +1,88 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +/** + * Generic Bad Credentials 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 Credentials + * (BadUsernamePassowrdCredentials). + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class BadCredentialsAuthenticationException extends + AuthenticationException { + + /** + * 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 static final String CODE = "error.authentication.credentials.bad"; + + /** + * 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 0000000..f61e43d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationException.java @@ -0,0 +1,81 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.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 0000000..d25d4fe --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationException.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.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 0000000..95c074c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationException.java @@ -0,0 +1,81 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +/** + * Exception to represent credentials that have been blocked for a reason such + * as Locked account. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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/DefaultPasswordEncoder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoder.java new file mode 100644 index 0000000..601b7dd --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoder.java @@ -0,0 +1,96 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import org.springframework.util.StringUtils; + +import javax.validation.constraints.NotNull; +import java.io.UnsupportedEncodingException; +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 + * @version $Revision$ $Date$ + * @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'}; + + @NotNull + private final String encodingAlgorithm; + + private String characterEncoding; + + public DefaultPasswordEncoder(final String encodingAlgorithm) { + this.encodingAlgorithm = encodingAlgorithm; + } + + public String encode(final String password) { + if (password == null) { + return null; + } + + try { + MessageDigest messageDigest = MessageDigest + .getInstance(this.encodingAlgorithm); + + if (StringUtils.hasText(this.characterEncoding)) { + messageDigest.update(password.getBytes(this.characterEncoding)); + } else { + messageDigest.update(password.getBytes()); + } + + + 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(byte[] bytes) { + final StringBuilder buf = new StringBuilder(bytes.length * 2); + + for (int j = 0; j < bytes.length; j++) { + buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]); + buf.append(HEX_DIGITS[bytes[j] & 0x0f]); + } + return buf.toString(); + } + + public final void setCharacterEncoding(final String characterEncoding) { + this.characterEncoding = characterEncoding; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NamedAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NamedAuthenticationHandler.java new file mode 100644 index 0000000..a823c34 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NamedAuthenticationHandler.java @@ -0,0 +1,33 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +/** + * Offers AuthenticationHandlers a way to identify themselves by a + * user-configured name. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public interface NamedAuthenticationHandler extends AuthenticationHandler { + + String getName(); +} 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 0000000..2beb0b1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/NoOpPrincipalNameTransformer.java @@ -0,0 +1,33 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +/** + * Simple implementation that actually does NO transformation. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.3.6 + */ +public final class NoOpPrincipalNameTransformer implements PrincipalNameTransformer { + + public String transform(final String formUserId) { + return formUserId; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PasswordEncoder.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PasswordEncoder.java new file mode 100644 index 0000000..859df16 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PasswordEncoder.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.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/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 0000000..c910ca5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoder.java @@ -0,0 +1,34 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class PlainTextPasswordEncoder implements PasswordEncoder { + + 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 0000000..631f4e9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PrefixSuffixPrincipalNameTransformer.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +package org.jasig.cas.authentication.handler; + +/** + * Transform the user id by adding a prefix or suffix. + * + * @author Howard Gilbert + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.3.6 + */ + +public final class PrefixSuffixPrincipalNameTransformer implements PrincipalNameTransformer { + + private String prefix; + + private String suffix; + + 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/PrincipalNameTransformer.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PrincipalNameTransformer.java new file mode 100644 index 0000000..b8059c8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/PrincipalNameTransformer.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + + +/** + * @author Howard Gilbert + * @version $Revision$ $Date$ + * @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 CredentialsToPrincipalResolver 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 + */ + public String transform(String formUserId); +} + 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 0000000..b54d3f4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UncategorizedAuthenticationException.java @@ -0,0 +1,54 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public abstract class UncategorizedAuthenticationException extends + AuthenticationException { + + /** + * 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 0000000..dc10bfb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationException.java @@ -0,0 +1,81 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +/** + * The exception to throw when we explicitly don't know anything about the + * username. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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 0000000..0e7b364 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsException.java @@ -0,0 +1,59 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.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 0000000..cc4877b --- /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 credentials. 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 credentials are a Userid and Password, then it submits them to an +external Kerberos, LDAP, or JDBC authority for validation.
  • +
  • If the credentials 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 0000000..7f2010c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractPreAndPostProcessingAuthenticationHandler.java @@ -0,0 +1,92 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.NamedAuthenticationHandler; +import org.jasig.cas.authentication.principal.Credentials; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractPreAndPostProcessingAuthenticationHandler + implements NamedAuthenticationHandler { + + /** Instance of logging for subclasses. */ + protected Logger log = LoggerFactory.getLogger(this.getClass()); + + /** The name of the authentication handler. */ + @NotNull + private String name = getClass().getName(); + + /** + * Method to execute before authentication occurs. + * + * @param credentials the Credentials supplied + * @return true if authentication should continue, false otherwise. + */ + protected boolean preAuthenticate(final Credentials credentials) { + return true; + } + + /** + * Method to execute after authentication occurs. + * + * @param credentials the supplied credentials + * @param authenticated the result of the authentication attempt. + * @return true if the handler should return true, false otherwise. + */ + protected boolean postAuthenticate(final Credentials credentials, + final boolean authenticated) { + return authenticated; + } + + public final void setName(final String name) { + this.name = name; + } + + public final String getName() { + return this.name; + } + + public final boolean authenticate(final Credentials credentials) + throws AuthenticationException { + + if (!preAuthenticate(credentials)) { + return false; + } + + final boolean authenticated = doAuthentication(credentials); + + return postAuthenticate(credentials, authenticated); + } + + protected abstract boolean doAuthentication(final Credentials credentials) + throws AuthenticationException; +} 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 0000000..18e3a31 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/AbstractUsernamePasswordAuthenticationHandler.java @@ -0,0 +1,144 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.authentication.handler.*; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; + +import javax.validation.constraints.NotNull; + +/** + * Abstract class to override supports so that we don't need to duplicate the + * check for UsernamePasswordCredentials. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public abstract class AbstractUsernamePasswordAuthenticationHandler extends + AbstractPreAndPostProcessingAuthenticationHandler { + + /** Default class to support if one is not supplied. */ + private static final Class DEFAULT_CLASS = UsernamePasswordCredentials.class; + + /** Class that this instance will support. */ + @NotNull + private Class< ? > classToSupport = DEFAULT_CLASS; + + /** + * Boolean to determine whether to support subclasses of the class to + * support. + */ + private boolean supportSubClasses = true; + + /** + * 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(); + + /** + * Method automatically handles conversion to UsernamePasswordCredentials + * and delegates to abstract authenticateUsernamePasswordInternal so + * subclasses do not need to cast. + */ + protected final boolean doAuthentication(final Credentials credentials) + throws AuthenticationException { + return authenticateUsernamePasswordInternal((UsernamePasswordCredentials) credentials); + } + + /** + * Abstract convenience method that assumes the credentials passed in are a + * subclass of UsernamePasswordCredentials. + * + * @param credentials the credentials representing the Username and Password + * presented to CAS + * @return true if the credentials are authentic, false otherwise. + * @throws AuthenticationException if authenticity cannot be determined. + */ + protected abstract boolean authenticateUsernamePasswordInternal( + final UsernamePasswordCredentials credentials) + throws AuthenticationException; + + /** + * 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; + } + + /** + * Method to set the class to support. + * + * @param classToSupport the class we want this handler to support + * explicitly. + */ + public final void setClassToSupport(final Class< ? > classToSupport) { + this.classToSupport = classToSupport; + } + + /** + * Method to set whether this handler will support subclasses of the + * supported class. + * + * @param supportSubClasses boolean of whether to support subclasses or not. + */ + public final void setSupportSubClasses(final boolean supportSubClasses) { + this.supportSubClasses = supportSubClasses; + } + + /** + * 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; + } + + /** + * @return true if the credentials are not null and the credentials class is + * equal to the class defined in classToSupport. + */ + public final boolean supports(final Credentials credentials) { + return credentials != null + && (this.classToSupport.equals(credentials.getClass()) || (this.classToSupport + .isAssignableFrom(credentials.getClass())) + && this.supportSubClasses); + } +} 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 0000000..8aff61d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandler.java @@ -0,0 +1,99 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.authentication.handler.AuthenticationHandler; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.HttpBasedServiceCredentials; +import org.jasig.cas.util.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; + +/** + * Class to validate the credentials 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class HttpBasedServiceCredentialsAuthenticationHandler implements AuthenticationHandler { + + /** The string representing the HTTPS protocol. */ + private static final String PROTOCOL_HTTPS = "https"; + + /** Boolean variable denoting whether secure connection is required or not. */ + private boolean requireSecure = true; + + /** Log instance. */ + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** Instance of Apache Commons HttpClient */ + @NotNull + private HttpClient httpClient; + + public boolean authenticate(final Credentials credentials) { + final HttpBasedServiceCredentials serviceCredentials = (HttpBasedServiceCredentials) credentials; + if (this.requireSecure + && !serviceCredentials.getCallbackUrl().getProtocol().equals( + PROTOCOL_HTTPS)) { + if (log.isDebugEnabled()) { + log.debug("Authentication failed because url was not secure."); + } + return false; + } + log + .debug("Attempting to resolve credentials for " + + serviceCredentials); + + return this.httpClient.isValidEndPoint(serviceCredentials + .getCallbackUrl()); + } + + /** + * @return true if the credentials provided are not null and the credentials + * are a subclass of (or equal to) HttpBasedServiceCredentials. + */ + public boolean supports(final Credentials credentials) { + return credentials != null + && HttpBasedServiceCredentials.class.isAssignableFrom(credentials + .getClass()); + } + + /** Sets the HttpClient which will do all of the connection stuff. */ + public void setHttpClient(final HttpClient httpClient) { + this.httpClient = httpClient; + } + + /** + * Set whether a secure url is required or not. + * + * @param requireSecure true if its required, false if not. Default is true. + */ + public void setRequireSecure(final boolean requireSecure) { + this.requireSecure = requireSecure; + } +} 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 0000000..d60e868 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandler.java @@ -0,0 +1,151 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +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.security.auth.login.LoginException; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.springframework.util.Assert; + +/** + * 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 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 + * @version $Revision$ $Date$ + * @since 3.0.5 + * @see javax.security.auth.callback.CallbackHandler + * @see javax.security.auth.callback.PasswordCallback + * @see javax.security.auth.callback.NameCallback + */ +public class JaasAuthenticationHandler extends + AbstractUsernamePasswordAuthenticationHandler { + + /** If no realm is specified, we default to CAS. */ + private static final String DEFAULT_REALM = "CAS"; + + /** The realm that contains the login module information. */ + @NotNull + private String realm = DEFAULT_REALM; + + public JaasAuthenticationHandler() { + Assert.notNull(Configuration.getConfiguration(), "Static Configuration cannot be null. Did you remember to specify \"java.security.auth.login.config\"?"); + } + + protected final boolean authenticateUsernamePasswordInternal( + final UsernamePasswordCredentials credentials) + throws AuthenticationException { + + final String transformedUsername = getPrincipalNameTransformer().transform(credentials.getUsername()); + + try { + if (log.isDebugEnabled()) { + log.debug("Attempting authentication for: " + + transformedUsername); + } + final LoginContext lc = new LoginContext(this.realm, + new UsernamePasswordCallbackHandler(transformedUsername, + credentials.getPassword())); + + lc.login(); + lc.logout(); + } catch (final LoginException fle) { + if (log.isDebugEnabled()) { + log.debug("Authentication failed for: " + + transformedUsername); + } + return false; + } + + if (log.isDebugEnabled()) { + log.debug("Authentication succeeded for: " + + transformedUsername); + } + return true; + } + + public void setRealm(final String realm) { + this.realm = realm; + } + + /** + * 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; + + /** + * Constuctor 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; + + } + + 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/SimpleTestUsernamePasswordAuthenticationHandler.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordAuthenticationHandler.java new file mode 100644 index 0000000..d9baab8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordAuthenticationHandler.java @@ -0,0 +1,59 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +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 Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class SimpleTestUsernamePasswordAuthenticationHandler extends + AbstractUsernamePasswordAuthenticationHandler { + + public SimpleTestUsernamePasswordAuthenticationHandler() { + log + .warn(this.getClass().getName() + + " is only to be used in a testing environment. NEVER enable this in a production environment."); + } + + public boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) { + final String username = credentials.getUsername(); + final String password = credentials.getPassword(); + + if (StringUtils.hasText(username) && StringUtils.hasText(password) + && username.equals(getPasswordEncoder().encode(password))) { + log + .debug("User [" + username + + "] was successfully authenticated."); + return true; + } + + log.debug("User [" + username + "] failed authentication"); + + return false; + } +} 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 0000000..bd1f0f4 --- /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 0000000..9cf1823 --- /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/AbstractPersonDirectoryCredentialsToPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/AbstractPersonDirectoryCredentialsToPrincipalResolver.java new file mode 100644 index 0000000..426a118 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/AbstractPersonDirectoryCredentialsToPrincipalResolver.java @@ -0,0 +1,110 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public abstract class AbstractPersonDirectoryCredentialsToPrincipalResolver + implements CredentialsToPrincipalResolver { + + /** Log instance. */ + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + + private boolean returnNullIfNoAttributes = false; + + /** Repository of principal attributes to be retrieved */ + @NotNull + private IPersonAttributeDao attributeRepository = new StubPersonAttributeDao(new HashMap>()); + + public final Principal resolvePrincipal(final Credentials credentials) { + if (log.isDebugEnabled()) { + log.debug("Attempting to resolve a principal..."); + } + + final String principalId = extractPrincipalId(credentials); + + if (principalId == null) { + return null; + } + + if (log.isDebugEnabled()) { + log.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 new SimplePrincipal(principalId); + } + + if (attributes == null) { + return null; + } + + final Map convertedAttributes = new HashMap(); + + for (final Map.Entry> entry : attributes.entrySet()) { + final String key = entry.getKey(); + final Object value = entry.getValue().size() == 1 ? entry.getValue().get(0) : entry.getValue(); + convertedAttributes.put(key, value); + } + return new SimplePrincipal(principalId, convertedAttributes); + } + + /** + * Extracts the id of the user from the provided credentials. + * + * @param credentials the credentials provided by the user. + * @return the username, or null if it could not be resolved. + */ + protected abstract String extractPrincipalId(Credentials credentials); + + public final void setAttributeRepository(final IPersonAttributeDao attributeRepository) { + this.attributeRepository = attributeRepository; + } + + public void setReturnNullIfNoAttributes(final boolean returnNullIfNoAttributes) { + this.returnNullIfNoAttributes = returnNullIfNoAttributes; + } +} 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 0000000..1d8f322 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/AbstractWebApplicationService.java @@ -0,0 +1,169 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.HttpClient; +import org.jasig.cas.util.SamlUtils; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract implementation of a WebApplicationService. + * + * @author Scott Battaglia + * @version $Revision: 1.3 $ $Date: 2007/04/19 20:13:01 $ + * @since 3.1 + * + */ +public abstract class AbstractWebApplicationService implements WebApplicationService { + + protected static final Logger LOG = LoggerFactory.getLogger(SamlService.class); + + private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap()); + + private static final UniqueTicketIdGenerator GENERATOR = new DefaultUniqueTicketIdGenerator(); + + /** 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 = false; + + private final HttpClient httpClient; + + protected AbstractWebApplicationService(final String id, final String originalUrl, final String artifactId, final HttpClient httpClient) { + this.id = id; + this.originalUrl = originalUrl; + this.artifactId = artifactId; + this.httpClient = httpClient; + } + + 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; + } + + 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); + } + + protected final String getOriginalUrl() { + return this.originalUrl; + } + + protected final HttpClient getHttpClient() { + return this.httpClient; + } + + 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; + } + + public int hashCode() { + final int prime = 41; + int result = 1; + result = prime * result + + ((this.id == null) ? 0 : this.id.hashCode()); + return result; + } + + protected Principal getPrincipal() { + return this.principal; + } + + public void setPrincipal(final Principal principal) { + this.principal = principal; + } + + public boolean matches(final Service service) { + return this.id.equals(service.getId()); + } + + public synchronized boolean logOutOfService(final String sessionIdentifier) { + if (this.loggedOutAlready) { + return true; + } + + LOG.debug("Sending logout request for: " + getId()); + + final String logoutRequest = "@NOT_USED@" + + sessionIdentifier + ""; + + this.loggedOutAlready = true; + + if (this.httpClient != null) { + return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest, true); + } + + return false; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Credentials.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Credentials.java new file mode 100644 index 0000000..4a9d3d3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Credentials.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.io.Serializable; + +/** + * Marker interface for credentials required to authenticate a principal. + *

+ * The Credentials is an opaque object that represents the information a user + * asserts proves that the user is who it says it is. In CAS, any information + * that is to be presented for authentication must be wrapped (or implement) the + * Credentials interface. Credentials can contain a userid and password, or a + * Certificate, or an IP address, or a cookie value. Some credentials require + * validation, while others (such as container based or Filter based validation) + * are inherently trustworthy. + *

+ * People who choose to implement their own Credentials object should take care that + * any toString() they implement does not accidentally expose confidential information. + * toString() can be called from various portions of the CAS code base, including logging + * statements, and thus toString should never contain anything confidential or anything + * that should not be logged. + *

+ * Credentials objects that are included in CAS do NOT expose any confidential information. + * + * @author William G. Thompson, Jr. + * @version $Revision: 1.2 $ $Date: 2007/01/22 20:35:26 $ + * @since 3.0 + *

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

+ */ +public interface Credentials extends Serializable { + // marker interface contains no methods +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/CredentialsToPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/CredentialsToPrincipalResolver.java new file mode 100644 index 0000000..ad32215 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/CredentialsToPrincipalResolver.java @@ -0,0 +1,75 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * CredentialsToPrincipalResolvers extract information from the Credentials + * provided and determine the Principal represented by those credentials. + *

+ * A minimal Principal object just has one ID value. This can be extended with + * richer objects containing more properties. The SimplePrincipal class + * implementing this interface just stores a userid. + *

+ *

+ * The Credentials typically contains a userid typed by the user or a + * Certificate presented by the browser. In the simplest case the userid is + * stored as the Principal ID. The Certificate is a more complicated case + * because the ID may have to be extracted from the Subject DN or from one of + * the alternate subject names. In a few cases, the institution may prefer the + * ID to be a student or employee ID number that can only be obtained by + * database lookup using information supplied in the Credentials. + *

+ *

+ * The Resolver is free to obtain additional information about the user and + * place it in the fields of a class that extends Principal. Such extended + * information will be stored like other Principal objects in the TGT, persisted + * as needed, and will be available to the View layer, but it is transparent to + * most CAS processing. + *

+ * + * @author Scott Battaglia + * @version $Revision: 1.2 $ $Date: 2007/01/22 20:35:27 $ + * @since 3.0 + *

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

+ * @see org.jasig.cas.authentication.principal.Principal + * @see org.jasig.cas.authentication.principal.Credentials + */ +public interface CredentialsToPrincipalResolver { + + /** + * Turn Credentials into a Principal object by analyzing the information + * provided in the Credentials and constructing a Principal object based on + * that information or information derived from the Credentials object. + * + * @param credentials from which to resolve Principal + * @return resolved Principal, or null if the principal could not be resolved. + */ + Principal resolvePrincipal(Credentials credentials); + + /** + * Determine if a credentials type is supported by this resolver. This is + * checked before calling resolve principal. + * + * @param credentials The credentials to check if we support. + * @return true if we support these credentials, false otherwise. + */ + boolean supports(Credentials credentials); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/GoogleAccountsService.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/GoogleAccountsService.java new file mode 100644 index 0000000..ae6e666 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/GoogleAccountsService.java @@ -0,0 +1,304 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.util.SamlUtils; +import org.jdom.Document; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import org.apache.commons.codec.binary.Base64; + +/** + * Implementation of a Service that supports Google Accounts (eventually a more + * generic SAML2 support will come). + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public class GoogleAccountsService extends AbstractWebApplicationService { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 6678711809842282833L; + + private static Random random = new Random(); + + private static final char[] charMapping = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p'}; + + private static final String CONST_PARAM_SERVICE = "SAMLRequest"; + + private static final String CONST_RELAY_STATE = "RelayState"; + + private static final String TEMPLATE_SAML_RESPONSE = "\" IssueInstant=\"\" Version=\"2.0\"" + + " xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"" + + " xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"" + + " xmlns:xenc=\"http://www.w3.org/2001/04/xmlenc#\">" + + "" + + "" + + "" + + "\"" + + " IssueInstant=\"2003-04-17T00:46:02Z\" Version=\"2.0\"" + + " xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\">" + + "https://www.opensaml.org/IDP" + + "" + + "" + + "" + + "" + + "" + + "\" NotOnOrAfter=\"\" InResponseTo=\"\" />" + + "" + + "" + + "\">" + + "" + + "" + + "" + + "" + + "\">" + + "" + + "" + + "urn:oasis:names:tc:SAML:2.0:ac:classes:Password" + + "" + + "" + + "" + + ""; + + private final String relayState; + + private final PublicKey publicKey; + + private final PrivateKey privateKey; + + private final String requestId; + + private final String alternateUserName; + + protected GoogleAccountsService(final String id, final String relayState, final String requestId, + final PrivateKey privateKey, final PublicKey publicKey, final String alternateUserName) { + this(id, id, null, relayState, requestId, privateKey, publicKey, alternateUserName); + } + + protected GoogleAccountsService(final String id, final String originalUrl, + final String artifactId, final String relayState, final String requestId, + final PrivateKey privateKey, final PublicKey publicKey, final String alternateUserName) { + super(id, originalUrl, artifactId, null); + this.relayState = relayState; + this.privateKey = privateKey; + this.publicKey = publicKey; + this.requestId = requestId; + this.alternateUserName = alternateUserName; + } + + public static GoogleAccountsService createServiceFrom( + final HttpServletRequest request, final PrivateKey privateKey, + final PublicKey publicKey, final String alternateUserName) { + final String relayState = request.getParameter(CONST_RELAY_STATE); + + final String xmlRequest = decodeAuthnRequestXML(request + .getParameter(CONST_PARAM_SERVICE)); + + if (!StringUtils.hasText(xmlRequest)) { + return null; + } + + final Document document = SamlUtils + .constructDocumentFromXmlString(xmlRequest); + + if (document == null) { + return null; + } + + final String assertionConsumerServiceUrl = document.getRootElement().getAttributeValue("AssertionConsumerServiceURL"); + final String requestId = document.getRootElement().getAttributeValue("ID"); + + return new GoogleAccountsService(assertionConsumerServiceUrl, + relayState, requestId, privateKey, publicKey, alternateUserName); + } + + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap(); + final String samlResponse = constructSamlResponse(); + final String signedResponse = SamlUtils.signSamlResponse(samlResponse, + this.privateKey, this.publicKey); + parameters.put("SAMLResponse", signedResponse); + parameters.put("RelayState", this.relayState); + + return Response.getPostResponse(getOriginalUrl(), parameters); + } + + /** + * Service does not support Single Log Out + * + * @see org.jasig.cas.authentication.principal.WebApplicationService#logOutOfService(java.lang.String) + */ + public boolean logOutOfService(final String sessionIdentifier) { + return false; + } + + private String constructSamlResponse() { + String samlResponse = TEMPLATE_SAML_RESPONSE; + + final Calendar c = Calendar.getInstance(); + c.setTime(new Date()); + c.add(Calendar.YEAR, 1); + + final String userId; + + if (this.alternateUserName == null) { + userId = getPrincipal().getId(); + } else { + final String attributeValue = (String) getPrincipal().getAttributes().get(this.alternateUserName); + if (attributeValue == null) { + userId = getPrincipal().getId(); + } else { + userId = attributeValue; + } + } + + samlResponse = samlResponse.replace("", userId); + samlResponse = samlResponse.replace("", createID()); + samlResponse = samlResponse.replace("", SamlUtils + .getCurrentDateAndTime()); + samlResponse = samlResponse.replace("", SamlUtils + .getCurrentDateAndTime()); + samlResponse = samlResponse.replaceAll("", SamlUtils + .getFormattedDateAndTime(c.getTime())); + samlResponse = samlResponse.replace("", createID()); + samlResponse = samlResponse.replaceAll("", getId()); + samlResponse = samlResponse.replace("", this.requestId); + + return samlResponse; + } + + private static String createID() { + final byte[] bytes = new byte[20]; // 160 bits + random.nextBytes(bytes); + + final char[] chars = new char[40]; + + for (int i = 0; i < bytes.length; i++) { + int left = (bytes[i] >> 4) & 0x0f; + int right = bytes[i] & 0x0f; + chars[i * 2] = charMapping[left]; + chars[i * 2 + 1] = charMapping[right]; + } + + return String.valueOf(chars); + } + + private static String decodeAuthnRequestXML( + final String encodedRequestXmlString) { + if (encodedRequestXmlString == null) { + return null; + } + + final byte[] decodedBytes = base64Decode(encodedRequestXmlString); + + if (decodedBytes == null) { + return null; + } + + final String inflated = inflate(decodedBytes); + + if (inflated != null) { + return inflated; + } + + return zlibDeflate(decodedBytes); + } + + private static String zlibDeflate(final byte[] bytes) { + final ByteArrayInputStream bais = new ByteArrayInputStream(bytes); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + final InflaterInputStream iis = new InflaterInputStream(bais); + final byte[] buf = new byte[1024]; + + try { + int count = iis.read(buf); + while (count != -1) { + baos.write(buf, 0, count); + count = iis.read(buf); + } + return new String(baos.toByteArray()); + } catch (final Exception e) { + return null; + } finally { + try { + iis.close(); + } catch (final Exception e) { + // nothing to do + } + } + } + + private static byte[] base64Decode(final String xml) { + try { + final byte[] xmlBytes = xml.getBytes("UTF-8"); + return Base64.decodeBase64(xmlBytes); + } catch (final Exception e) { + return null; + } + } + + private static String inflate(final byte[] bytes) { + final Inflater inflater = new Inflater(true); + final byte[] xmlMessageBytes = new byte[10000]; + + final byte[] extendedBytes = new byte[bytes.length + 1]; + System.arraycopy(bytes, 0, extendedBytes, 0, bytes.length); + extendedBytes[bytes.length] = 0; + + inflater.setInput(extendedBytes); + + try { + final int resultLength = inflater.inflate(xmlMessageBytes); + inflater.end(); + + if (!inflater.finished()) { + throw new RuntimeException("buffer not large enough."); + } + + inflater.end(); + return new String(xmlMessageBytes, 0, resultLength, "UTF-8"); + } catch (final DataFormatException e) { + return null; + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("Cannot find encoding: UTF-8", e); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentials.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentials.java new file mode 100644 index 0000000..7b3b974 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentials.java @@ -0,0 +1,101 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.net.URL; + +import org.springframework.util.Assert; + +/** + * The Credentials representing an HTTP-based service. HTTP-based services (such + * as web applications) are often represented by the URL entry point of the + * application. + * + * @author Scott Battaglia + * @version $Revision: 1.3 $ $Date: 2007/04/24 13:01:51 $ + * @since 3.0 + */ +public class HttpBasedServiceCredentials implements Credentials { + + /** Unique Serializable ID. */ + private static final long serialVersionUID = 3904681574350991665L; + + /** The callbackURL to check that identifies the application. */ + private final URL callbackUrl; + + /** String form of callbackUrl; */ + private final String callbackUrlAsString; + + /** + * Constructor that takes the URL of the HTTP-based service and creates the + * Credentials object. Caches the value of URL.toExternalForm so updates to + * the URL will not be reflected in a call to toString(). + * + * @param callbackUrl the URL representing the service + * @throws IllegalArgumentException if the callbackUrl is null. + */ + public HttpBasedServiceCredentials(final URL callbackUrl) { + Assert.notNull(callbackUrl, "callbackUrl cannot be null"); + this.callbackUrl = callbackUrl; + this.callbackUrlAsString = callbackUrl.toExternalForm(); + } + + /** + * @return Returns the callbackUrl. + */ + public final URL getCallbackUrl() { + return this.callbackUrl; + } + + /** + * Returns the String version of the URL, based on the original URL + * provided. i.e. this caches the value of URL.toExternalForm() + */ + public final String toString() { + return "[callbackUrl: " + this.callbackUrlAsString + "]"; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((this.callbackUrlAsString == null) ? 0 : this.callbackUrlAsString + .hashCode()); + return result; + } + + public boolean equals(final Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + final HttpBasedServiceCredentials other = (HttpBasedServiceCredentials) obj; + if (this.callbackUrlAsString == null) { + if (other.callbackUrlAsString != null) + return false; + } else if (!this.callbackUrlAsString.equals(other.callbackUrlAsString)) + return false; + return true; + } + + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsToPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsToPrincipalResolver.java new file mode 100644 index 0000000..9f6168f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsToPrincipalResolver.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * HttpBasedServiceCredentialsToPrincipalResolver extracts the callbackUrl from + * the HttpBasedServiceCredentials and constructs a SimpleService with the + * callbackUrl as the unique Id. + * + * @author Scott Battaglia + * @version $Revision: 1.5 $ $Date: 2007/02/27 19:31:58 $ + * @since 3.0 + */ +public final class HttpBasedServiceCredentialsToPrincipalResolver implements + CredentialsToPrincipalResolver { + + /** + * Method to return a simple Service Principal with the identifier set to be + * the callback url. + */ + public Principal resolvePrincipal(final Credentials credentials) { + final HttpBasedServiceCredentials serviceCredentials = (HttpBasedServiceCredentials) credentials; + return new SimpleWebApplicationServiceImpl(serviceCredentials.getCallbackUrl().toExternalForm()); + } + + /** + * @return true if the credentials provided are not null and are assignable + * from HttpBasedServiceCredentials, otherwise returns false. + */ + public boolean supports(final Credentials credentials) { + return credentials != null + && HttpBasedServiceCredentials.class.isAssignableFrom(credentials + .getClass()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/PersistentIdGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/PersistentIdGenerator.java new file mode 100644 index 0000000..ee37507 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/PersistentIdGenerator.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * Generates a unique consistant Id based on the principal, a service, and some + * algorithm. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2007/04/20 19:39:31 $ + * @since 3.1 + */ +public interface PersistentIdGenerator { + + /** + * Generates a PersistentId based on some algorithm plus the principal and + * service. + * + * @param principal the principal to generate the id for. + * @param service the service to generate the id for. + * @return the generated persistent id. + */ + String generate(Principal principal, Service service); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Principal.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Principal.java new file mode 100644 index 0000000..08de348 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Principal.java @@ -0,0 +1,54 @@ +/* + * 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. + */ +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 + * @version $Revision: 1.3 $ $Date: 2007/04/19 20:13:01 $ + * @since 3.0 + *

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

+ */ +public interface Principal extends Serializable { + + /** + * Returns the unique id for the Principal + * @return the unique id for the Principal. + */ + String getId(); + + /** + * + * @return + */ + Map getAttributes(); +} 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 0000000..fc7ed1c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulator.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; + +/** + * Determines if the credentials provided are for Remember Me Services and then sets the appropriate + * Authentication attribute if remember me services have been requested. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public final class RememberMeAuthenticationMetaDataPopulator implements + AuthenticationMetaDataPopulator { + + public Authentication populateAttributes(final Authentication authentication, + final Credentials credentials) { + if (credentials instanceof RememberMeCredentials) { + final RememberMeCredentials r = (RememberMeCredentials) credentials; + if (r.isRememberMe()) { + authentication.getAttributes().put(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME, Boolean.TRUE); + } + } + + return authentication; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeCredentials.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeCredentials.java new file mode 100644 index 0000000..5496caa --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeCredentials.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * Credentials that wish to handle remember me scenarios need + * to implement this class. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public interface RememberMeCredentials extends Credentials { + + String AUTHENTICATION_ATTRIBUTE_REMEMBER_ME = "org.jasig.cas.authentication.principal.REMEMBER_ME"; + + String REQUEST_PARAMETER_REMEMBER_ME = "rememberMe"; + + boolean isRememberMe(); + + void setRememberMe(boolean rememberMe); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeUsernamePasswordCredentials.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeUsernamePasswordCredentials.java new file mode 100644 index 0000000..c59b98b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/RememberMeUsernamePasswordCredentials.java @@ -0,0 +1,64 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * Handles both remember me services and username and password. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public class RememberMeUsernamePasswordCredentials extends + UsernamePasswordCredentials implements RememberMeCredentials { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -9178853167397038282L; + + private boolean rememberMe; + + public final boolean isRememberMe() { + return this.rememberMe; + } + + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + (this.rememberMe ? 1231 : 1237); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + final RememberMeUsernamePasswordCredentials other = (RememberMeUsernamePasswordCredentials) obj; + if (this.rememberMe != other.rememberMe) + return false; + return true; + } + + public final void setRememberMe(boolean rememberMe) { + this.rememberMe = rememberMe; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Response.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Response.java new file mode 100644 index 0000000..73fbeeb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Response.java @@ -0,0 +1,135 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.net.URLEncoder; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Encapsulates a Response to send back for a particular service. + * + * @author Scott Battaglia + * @author Arnaud Lesueur + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public final class Response { + /** Pattern to detect unprintable ASCII characters. */ + private static final Pattern NON_PRINTABLE = + Pattern.compile("[\\x00-\\x19\\x7F]+"); + + /** Log instance. */ + protected static final Logger LOG = LoggerFactory.getLogger(Response.class); + + public static enum ResponseType { + POST, REDIRECT + } + + private final ResponseType responseType; + + private final String url; + + private final Map attributes; + + protected Response(ResponseType responseType, final String url, final Map attributes) { + this.responseType = responseType; + this.url = url; + this.attributes = attributes; + } + + public static Response getPostResponse(final String url, final Map attributes) { + return new Response(ResponseType.POST, url, attributes); + } + + public static Response getRedirectResponse(final String url, final Map parameters) { + final StringBuilder builder = new StringBuilder(parameters.size() * 40 + 100); + 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 Response(ResponseType.REDIRECT, builder.toString(), parameters); + } + + public Map getAttributes() { + return this.attributes; + } + + public ResponseType getResponseType() { + return this.responseType; + } + + 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) { + LOG.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/SamlService.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SamlService.java new file mode 100644 index 0000000..f29fd36 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SamlService.java @@ -0,0 +1,172 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.io.BufferedReader; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jasig.cas.util.HttpClient; +import org.springframework.util.StringUtils; + +/** + * 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 + * @version $Revision: 1.6 $ $Date: 2007/02/27 19:31:58 $ + * @since 3.1 + */ +public final class SamlService extends AbstractWebApplicationService { + + private static final Log log = LogFactory.getLog(SamlService.class); + + /** Constant representing service. */ + private static final String CONST_PARAM_SERVICE = "TARGET"; + + /** Constant representing artifact. */ + private static final String CONST_PARAM_TICKET = "SAMLart"; + + 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 String requestId; + + /** + * Unique Id for serialization. + */ + private static final long serialVersionUID = -6867572626767140223L; + + protected SamlService(final String id) { + super(id, id, null, new HttpClient()); + } + + protected SamlService(final String id, final String originalUrl, final String artifactId, final HttpClient httpClient, final String requestId) { + super(id, originalUrl, artifactId, httpClient); + this.requestId = requestId; + } + + /** + * This always returns true because a SAML Service does not receive the TARGET value on validation. + */ + public boolean matches(final Service service) { + return true; + } + + public String getRequestID() { + return this.requestId; + } + + public static SamlService createServiceFrom( + final HttpServletRequest request, final HttpClient httpClient) { + final String service = request.getParameter(CONST_PARAM_SERVICE); + final String artifactId; + final String requestBody = getRequestBody(request); + final String requestId; + + if (!StringUtils.hasText(service) && !StringUtils.hasText(requestBody)) { + 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; + } + + if (log.isDebugEnabled()) { + log.debug("Attempted to extract Request from HttpServletRequest. Results:"); + log.debug(String.format("Request Body: %s", requestBody)); + log.debug(String.format("Extracted ArtifactId: %s", artifactId)); + log.debug(String.format("Extracted Request Id: %s", requestId)); + } + + return new SamlService(id, service, artifactId, httpClient, requestId); + } + + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap(); + + parameters.put(CONST_PARAM_TICKET, ticketId); + parameters.put(CONST_PARAM_SERVICE, getOriginalUrl()); + + return Response.getRedirectResponse(getOriginalUrl(), parameters); + } + + protected static String extractRequestId(final String requestBody) { + if (!requestBody.contains("RequestID")) { + return null; + } + + try { + final int position = requestBody.indexOf("RequestID=\"") + 11; + final int nextPosition = requestBody.indexOf("\"", position); + + return requestBody.substring(position, nextPosition); + } catch (final Exception e) { + log.debug("Exception parsing RequestID from request." ,e); + return null; + } + } + + protected static String getRequestBody(final HttpServletRequest request) { + final StringBuilder builder = new StringBuilder(); + try { + final BufferedReader reader = request.getReader(); + + String line; + while ((line = reader.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } catch (final Exception e) { + return null; + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Service.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Service.java new file mode 100644 index 0000000..1e75634 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/Service.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +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 + * @version $Revision: 1.2 $ $Date: 2007/01/22 20:35:26 $ + * @since 3.0 + *

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

+ */ +public interface Service extends Principal { + + void setPrincipal(Principal principal); + + boolean logOutOfService(String sessionIdentifier); + + boolean matches(Service service); +} 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 0000000..188f8b2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGenerator.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.codec.binary.Base64; + +import javax.validation.constraints.NotNull; + +/** + * Generates PersistentIds based on the Shibboleth algorithm. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2007/04/20 19:39:31 $ + * @since 3.1 + */ +public final class ShibbolethCompatiblePersistentIdGenerator implements + PersistentIdGenerator { + + private static final byte CONST_SEPARATOR = (byte) '!'; + + @NotNull + private byte[] salt; + + public String generate(final Principal principal, final Service service) { + try { + final MessageDigest md = MessageDigest.getInstance("SHA"); + md.update(service.getId().getBytes()); + md.update(CONST_SEPARATOR); + md.update(principal.getId().getBytes()); + md.update(CONST_SEPARATOR); + + return Base64.encodeBase64String(md.digest(this.salt)).replaceAll( + System.getProperty("line.separator"), ""); + } catch (final NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + public void setSalt(final String salt) { + this.salt = salt.getBytes(); + } +} 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 0000000..156e4f7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimplePrincipal.java @@ -0,0 +1,91 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +/** + * Simple implementation of a AttributePrincipal that exposes an unmodifiable + * map of attributes. + * + * @author Scott Battaglia + * @version $Revision: 1.3 $ $Date: 2007/04/19 20:13:01 $ + * @since 3.1 + */ +public class SimplePrincipal implements Principal { + + private static final Map EMPTY_MAP = Collections + .unmodifiableMap(new HashMap()); + + /** + * Unique Id for Serialization. + */ + private static final long serialVersionUID = -5265620187476296219L; + + /** The unique identifier for the principal. */ + private final String id; + + /** Map of attributes for the Principal. */ + private Map attributes; + + public SimplePrincipal(final String id) { + this(id, null); + } + + public SimplePrincipal(final String id, final Map attributes) { + Assert.notNull(id, "id cannot be null"); + this.id = id; + + this.attributes = attributes == null || attributes.isEmpty() + ? EMPTY_MAP : Collections.unmodifiableMap(attributes); + } + + /** + * Returns an immutable map. + */ + public Map getAttributes() { + return this.attributes; + } + + public String toString() { + return this.id; + } + + public int hashCode() { + return super.hashCode() ^ this.id.hashCode(); + } + + public final String getId() { + return this.id; + } + + public boolean equals(final Object o) { + if (o == null || !this.getClass().equals(o.getClass())) { + return false; + } + + final SimplePrincipal p = (SimplePrincipal) o; + + return this.id.equals(p.getId()); + } +} 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 0000000..fa13307 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImpl.java @@ -0,0 +1,106 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.Response.ResponseType; +import org.jasig.cas.util.HttpClient; +import org.springframework.util.StringUtils; + +/** + * Represents a service which wishes to use the CAS protocol. + * + * @author Scott Battaglia + * @version $Revision: 1.3 $ $Date: 2007/04/24 18:19:22 $ + * @since 3.1 + */ +public final class SimpleWebApplicationServiceImpl extends + AbstractWebApplicationService { + + 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 ResponseType responseType; + + /** + * Unique Id for Serialization + */ + private static final long serialVersionUID = 8334068957483758042L; + + public SimpleWebApplicationServiceImpl(final String id) { + this(id, id, null, null, null); + } + + public SimpleWebApplicationServiceImpl(final String id, final HttpClient httpClient) { + this(id, id, null, null, httpClient); + } + + private SimpleWebApplicationServiceImpl(final String id, + final String originalUrl, final String artifactId, + final ResponseType responseType, final HttpClient httpClient) { + super(id, originalUrl, artifactId, httpClient); + this.responseType = responseType; + } + + public static SimpleWebApplicationServiceImpl createServiceFrom(final HttpServletRequest request) { + return createServiceFrom(request, null); + } + + public static SimpleWebApplicationServiceImpl createServiceFrom( + final HttpServletRequest request, final HttpClient httpClient) { + final String targetService = request + .getParameter(CONST_PARAM_TARGET_SERVICE); + final String method = request.getParameter(CONST_PARAM_METHOD); + final String serviceToUse = StringUtils.hasText(targetService) + ? targetService : request.getParameter(CONST_PARAM_SERVICE); + + 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) ? ResponseType.POST + : ResponseType.REDIRECT, httpClient); + } + + public Response getResponse(final String ticketId) { + final Map parameters = new HashMap(); + + if (StringUtils.hasText(ticketId)) { + parameters.put(CONST_PARAM_TICKET, ticketId); + } + + if (ResponseType.POST == this.responseType) { + return Response.getPostResponse(getOriginalUrl(), parameters); + } + return Response.getRedirectResponse(getOriginalUrl(), parameters); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentials.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentials.java new file mode 100644 index 0000000..33657c0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentials.java @@ -0,0 +1,101 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +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 + * @version $Revision: 1.2 $ $Date: 2007/01/22 20:35:26 $ + * @since 3.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; + } + + 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; + + 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() { + int result = username != null ? username.hashCode() : 0; + result = 31 * result + (password != null ? password.hashCode() : 0); + return result; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsToPrincipalResolver.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsToPrincipalResolver.java new file mode 100644 index 0000000..6dbbf40 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsToPrincipalResolver.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * Implementation of CredentialsToPrincipalResolver for Credentials based on + * UsernamePasswordCredentials when a SimplePrincipal (username only) is + * sufficient. + *

+ * Implementation extracts the username from the Credentials provided and + * constructs a new SimplePrincipal with the unique id set to the username. + *

+ * + * @author Scott Battaglia + * @version $Revision: 1.2 $ $Date: 2007/01/22 20:35:26 $ + * @since 3.0 + * @see org.jasig.cas.authentication.principal.SimplePrincipal + */ +public final class UsernamePasswordCredentialsToPrincipalResolver extends + AbstractPersonDirectoryCredentialsToPrincipalResolver { + + protected String extractPrincipalId(final Credentials credentials) { + final UsernamePasswordCredentials usernamePasswordCredentials = (UsernamePasswordCredentials) credentials; + return usernamePasswordCredentials.getUsername(); + } + + /** + * Return true if Credentials are UsernamePasswordCredentials, false + * otherwise. + */ + public boolean supports(final Credentials credentials) { + return credentials != null + && UsernamePasswordCredentials.class.isAssignableFrom(credentials + .getClass()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/WebApplicationService.java b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/WebApplicationService.java new file mode 100644 index 0000000..dba04b0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/authentication/principal/WebApplicationService.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +/** + * Represents a service using CAS that comes from the web. + * + * @author Scott Battaglia + * @version $Revision: 1.3 $ $Date: 2007/02/27 19:31:58 $ + * @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(); +} 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 0000000..b91bb33 --- /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 credentials 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/monitor/AbstractCacheMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractCacheMonitor.java new file mode 100644 index 0000000..e8f5887 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractCacheMonitor.java @@ -0,0 +1,109 @@ +/* + * 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. + */ + +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; + } + + + 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; + } + + + 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 0000000..dc4f474 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractNamedMonitor.java @@ -0,0 +1,49 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +import org.apache.commons.lang.StringUtils; +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 { + /** 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 0000000..22f35b4 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/AbstractPoolMonitor.java @@ -0,0 +1,122 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +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; +import javax.validation.constraints.NotNull; + +/** + * 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} */ + public PoolStatus observe() { + final Future result = this.executor.submit(new Validator()); + 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 class Validator implements Callable { + public StatusCode call() throws Exception { + return checkPool(); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatistics.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatistics.java new file mode 100644 index 0000000..10cd37c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatistics.java @@ -0,0 +1,76 @@ +/* + * 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. + */ +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/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 0000000..3978995 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/CacheStatus.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package org.jasig.cas.monitor; + +/** + * 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, 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 this.statistics; + } + + + 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/DataSourceMonitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/DataSourceMonitor.java new file mode 100644 index 0000000..773975f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/DataSourceMonitor.java @@ -0,0 +1,89 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +import java.sql.ResultSet; +import java.sql.SQLException; +import javax.sql.DataSource; +import javax.validation.constraints.NotNull; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.ResultSetExtractor; + +/** + * 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, DataAccessException { + 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-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 0000000..d673c96 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthCheckMonitor.java @@ -0,0 +1,74 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.validation.constraints.NotNull; + +/** + * 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} */ + public String getName() { + return HealthCheckMonitor.class.getSimpleName(); + } + + /** {@inheritDoc} */ + 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(); + if (result.getCode().value() > code.value()) { + code = result.getCode(); + } + } 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 0000000..f58c670 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/HealthStatus.java @@ -0,0 +1,57 @@ +/* + * 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. + */ +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 0000000..746ab51 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryMonitor.java @@ -0,0 +1,67 @@ +/* + * 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. + */ +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; + + /** 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} */ + public String getName() { + return MemoryMonitor.class.getSimpleName(); + } + + + /** {@inheritDoc} */ + public MemoryStatus observe() { + final StatusCode code; + final long free = Runtime.getRuntime().freeMemory(); + final long total = Runtime.getRuntime().totalMemory(); + if (free * 100 / 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 0000000..2bed937 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/MemoryStatus.java @@ -0,0 +1,71 @@ +/* + * 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. + */ +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/Monitor.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/Monitor.java new file mode 100644 index 0000000..09a00f7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/Monitor.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +/** + * A monitor observes a resource and reports its status. + * + * @author Marvin S. Addison + * @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/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 0000000..ca898ad --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/PoolStatus.java @@ -0,0 +1,96 @@ +/* + * 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. + */ +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. + * + * @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; + } + + + 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 0000000..7f9d01d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionMonitor.java @@ -0,0 +1,114 @@ +/* + * 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. + */ +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 + */ + 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} */ + public String getName() { + return SessionMonitor.class.getSimpleName(); + } + + + /** {@inheritDoc} */ + 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 0000000..0e0cbc9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/SessionStatus.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +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 0000000..8fd1c7e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/SimpleCacheStatistics.java @@ -0,0 +1,117 @@ +/* + * 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. + */ + +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 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; + } + + + public int getPercentFree() { + if (this.capacity == 0) { + return 0; + } + return (int) ((this.capacity - this.size) * 100 / this.capacity); + } + + + public void toString(final StringBuilder builder) { + if (this.name != null) { + builder.append(this.name).append(':'); + } + 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/monitor/Status.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/Status.java new file mode 100644 index 0000000..eb18858 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/Status.java @@ -0,0 +1,92 @@ +/* + * 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. + */ +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/src/main/java/org/jasig/cas/monitor/StatusCode.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/StatusCode.java new file mode 100644 index 0000000..e338cfd --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/StatusCode.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +/** + * Monitor status code inspired by HTTP status codes. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +public enum StatusCode { + ERROR(500), + WARN(400), + INFO(300), + OK(200), + 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/src/main/java/org/jasig/cas/monitor/TicketRegistryState.java b/cas-server-core/src/main/java/org/jasig/cas/monitor/TicketRegistryState.java new file mode 100644 index 0000000..beb463c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/monitor/TicketRegistryState.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +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/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 0000000..5b1fa44 --- /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 0000000..026fee1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationService.java @@ -0,0 +1,130 @@ +/* + * 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. + */ +package org.jasig.cas.remoting.server; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.validation.Assertion; +import org.springframework.util.Assert; + +import javax.validation.*; +import javax.validation.constraints.NotNull; +import java.util.Set; + +/** + * Wrapper implementation around a CentralAuthenticationService that does + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class RemoteCentralAuthenticationService implements CentralAuthenticationService { + + /** The CORE to delegate to. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + /** The validators to check the Credentials. */ + @NotNull + private Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + + /** + * @throws IllegalArgumentException if the Credentials are null or if given + * invalid credentials. + */ + public String createTicketGrantingTicket(final Credentials credentials) throws TicketException { + Assert.notNull(credentials, "credentials cannot be null"); + checkForErrors(credentials); + + return this.centralAuthenticationService.createTicketGrantingTicket(credentials); + } + + public String grantServiceTicket(final String ticketGrantingTicketId, final Service service) throws TicketException { + return this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service); + } + + /** + * @throws IllegalArgumentException if given invalid credentials + */ + public String grantServiceTicket(final String ticketGrantingTicketId, final Service service, final Credentials credentials) throws TicketException { + checkForErrors(credentials); + + return this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service, credentials); + } + + public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws TicketException { + return this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service); + } + + public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) { + this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId); + } + + /** + * @throws IllegalArgumentException if the credentials are invalid. + */ + public String delegateTicketGrantingTicket(final String serviceTicketId, final Credentials credentials) throws TicketException { + checkForErrors(credentials); + + return this.centralAuthenticationService.delegateTicketGrantingTicket(serviceTicketId, credentials); + } + + private void checkForErrors(final Credentials credentials) { + if (credentials == null) { + return; + } + + final Set> errors = this.validator.validate(credentials); + 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 0000000..62f0ba0 --- /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/AbstractRegisteredService.java b/cas-server-core/src/main/java/org/jasig/cas/services/AbstractRegisteredService.java new file mode 100644 index 0000000..b5569af --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/AbstractRegisteredService.java @@ -0,0 +1,328 @@ +/* + * 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. + */ + +package org.jasig.cas.services; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.CompareToBuilder; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.hibernate.annotations.IndexColumn; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.DiscriminatorColumn; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.Table; +import javax.persistence.DiscriminatorType; +import javax.persistence.GeneratedValue; +import javax.persistence.JoinTable; +import javax.persistence.JoinColumn; +import javax.persistence.Column; +import javax.persistence.FetchType; + +/** + * Base class for mutable, persistable registered services. + * + * @author Marvin S. Addison + * @author Scott Battaglia + */ +@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, Serializable { + + /** Serialization version marker */ + private static final long serialVersionUID = 7645279151115635245L; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private long id = -1; + + @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) + @JoinTable(name = "rs_attributes", joinColumns = @JoinColumn(name = "RegisteredServiceImpl_id")) + @Column(name = "a_name", nullable = false) + @IndexColumn(name = "a_id") + private List allowedAttributes = new ArrayList(); + + private String description; + + protected String serviceId; + + private String name; + + private String theme; + + private boolean allowedToProxy = true; + + private boolean enabled = true; + + private boolean ssoEnabled = true; + + private boolean anonymousAccess = false; + + private boolean ignoreAttributes = false; + + @Column(name = "evaluation_order", nullable = false) + private int evaluationOrder; + + /** + * Name of the user attribute that this service expects as the value of the username payload in the + * validate responses. + */ + @Column(name = "username_attr", nullable = true, length = 256) + private String usernameAttribute = null; + + public boolean isAnonymousAccess() { + return this.anonymousAccess; + } + + public void setAnonymousAccess(final boolean anonymousAccess) { + this.anonymousAccess = anonymousAccess; + } + + public List getAllowedAttributes() { + return this.allowedAttributes; + } + + public long getId() { + return this.id; + } + + public String getDescription() { + return this.description; + } + + public String getServiceId() { + return this.serviceId; + } + + public String getName() { + return this.name; + } + + public String getTheme() { + return this.theme; + } + + public boolean isAllowedToProxy() { + return this.allowedToProxy; + } + + public boolean isEnabled() { + return this.enabled; + } + + public boolean isSsoEnabled() { + return this.ssoEnabled; + } + + public boolean equals(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.allowedToProxy, that.allowedToProxy) + .append(this.anonymousAccess, that.anonymousAccess) + .append(this.enabled, that.enabled) + .append(this.evaluationOrder, that.evaluationOrder) + .append(this.ignoreAttributes, that.ignoreAttributes) + .append(this.ssoEnabled, that.ssoEnabled) + .append(this.allowedAttributes, that.allowedAttributes) + .append(this.description, that.description) + .append(this.name, that.name) + .append(this.serviceId, that.serviceId) + .append(this.theme, that.theme) + .append(this.usernameAttribute, that.usernameAttribute) + .isEquals(); + } + + public int hashCode() { + return new HashCodeBuilder(7, 31) + .append(this.allowedAttributes) + .append(this.description) + .append(this.serviceId) + .append(this.name) + .append(this.theme) + .append(this.enabled) + .append(this.ssoEnabled) + .append(this.anonymousAccess) + .append(this.ignoreAttributes) + .append(this.evaluationOrder) + .append(this.usernameAttribute) + .toHashCode(); + } + + public void setAllowedAttributes(final List allowedAttributes) { + if (allowedAttributes == null) { + this.allowedAttributes = new ArrayList(); + } else { + this.allowedAttributes = allowedAttributes; + } + } + + public void setAllowedToProxy(final boolean allowedToProxy) { + this.allowedToProxy = allowedToProxy; + } + + public void setDescription(final String description) { + this.description = description; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + 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 setSsoEnabled(final boolean ssoEnabled) { + this.ssoEnabled = ssoEnabled; + } + + public void setTheme(final String theme) { + this.theme = theme; + } + + public boolean isIgnoreAttributes() { + return this.ignoreAttributes; + } + + public void setIgnoreAttributes(final boolean ignoreAttributes) { + this.ignoreAttributes = ignoreAttributes; + } + + public void setEvaluationOrder(final int evaluationOrder) { + this.evaluationOrder = evaluationOrder; + } + + public int getEvaluationOrder() { + return this.evaluationOrder; + } + + public String getUsernameAttribute() { + return this.usernameAttribute; + } + + /** + * Sets the name of the user attribute to use as the username when providing usernames to this registered service. + * + *

Note: The username attribute will have no affect on services that are marked for anonymous access. + * + * @param username attribute to release for this service that may be one of the following values: + *

    + *
  • name of the attribute this service prefers to consume as username
  • . + *
  • null to enforce default CAS behavior
  • + *
+ * @see #isAnonymousAccess() + */ + public void setUsernameAttribute(final String username) { + if (StringUtils.isBlank(username)) { + this.usernameAttribute = null; + } else { + this.usernameAttribute = username; + } + } + + public Object clone() throws CloneNotSupportedException { + 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.setAllowedAttributes(new ArrayList(source.getAllowedAttributes())); + this.setAllowedToProxy(source.isAllowedToProxy()); + this.setDescription(source.getDescription()); + this.setEnabled(source.isEnabled()); + this.setName(source.getName()); + this.setServiceId(source.getServiceId()); + this.setSsoEnabled(source.isSsoEnabled()); + this.setTheme(source.getTheme()); + this.setAnonymousAccess(source.isAnonymousAccess()); + this.setIgnoreAttributes(source.isIgnoreAttributes()); + this.setEvaluationOrder(source.getEvaluationOrder()); + this.setUsernameAttribute(source.getUsernameAttribute()); + } + + /** + * Compares this instance with the other registered service based on + * evaluation order, name. The name comparison is case insensitive. + * + * @see #getEvaluationOrder() + */ + public int compareTo(final RegisteredService other) { + return new CompareToBuilder() + .append(this.getEvaluationOrder(), other.getEvaluationOrder()) + .append(this.getName().toLowerCase(), other.getName().toLowerCase()) + .toComparison(); + } + + 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("usernameAttribute", this.usernameAttribute); + toStringBuilder.append("attributes", this.allowedAttributes.toArray()); + + return toStringBuilder.toString(); + } + + protected abstract AbstractRegisteredService newInstance(); +} 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 0000000..7bc5f33 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/DefaultServicesManagerImpl.java @@ -0,0 +1,170 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import com.github.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; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class DefaultServicesManagerImpl implements ReloadableServicesManager { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** Instance of ServiceRegistryDao. */ + @NotNull + private ServiceRegistryDao serviceRegistryDao; + + /** Map to store all services. */ + private ConcurrentHashMap services = new ConcurrentHashMap(); + + /** Default service to return if none have been registered. */ + private RegisteredService disabledRegisteredService; + + public DefaultServicesManagerImpl( + final ServiceRegistryDao serviceRegistryDao) { + this(serviceRegistryDao, new ArrayList()); + } + + /** + * 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. + * + * @param serviceRegistryDao the Service Registry Dao. + * @param defaultAttributes the list of default attributes to use. + */ + public DefaultServicesManagerImpl(final ServiceRegistryDao serviceRegistryDao, final List defaultAttributes) { + this.serviceRegistryDao = serviceRegistryDao; + this.disabledRegisteredService = constructDefaultRegisteredService(defaultAttributes); + + load(); + } + + @Transactional(readOnly = false) + @Audit(action = "DELETE_SERVICE", actionResolverName = "DELETE_SERVICE_ACTION_RESOLVER", resourceResolverName = "DELETE_SERVICE_RESOURCE_RESOLVER") + 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; + } + + /** + * Note, if the repository is empty, this implementation will return a default service to grant all access. + *

+ * This preserves default CAS behavior. + */ + public RegisteredService findServiceBy(final Service service) { + final Collection c = convertToTreeSet(); + + if (c.isEmpty()) { + return this.disabledRegisteredService; + } + + for (final RegisteredService r : c) { + if (r.matches(service)) { + return r; + } + } + + return null; + } + + public RegisteredService findServiceBy(final long id) { + final RegisteredService r = this.services.get(id); + + try { + return r == null ? null : (RegisteredService) r.clone(); + } catch (final CloneNotSupportedException e) { + return r; + } + } + + protected TreeSet convertToTreeSet() { + return new TreeSet(this.services.values()); + } + + public Collection getAllServices() { + return Collections.unmodifiableCollection(convertToTreeSet()); + } + + 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") + public synchronized RegisteredService save(final RegisteredService registeredService) { + final RegisteredService r = this.serviceRegistryDao.save(registeredService); + this.services.put(r.getId(), r); + return r; + } + + public void reload() { + log.info("Reloading registered services."); + load(); + } + + private void load() { + final ConcurrentHashMap localServices = new ConcurrentHashMap(); + + for (final RegisteredService r : this.serviceRegistryDao.load()) { + log.debug("Adding registered service " + r.getServiceId()); + localServices.put(r.getId(), r); + } + + this.services = localServices; + log.info(String.format("Loaded %s services.", this.services.size())); + } + + private RegisteredService constructDefaultRegisteredService(final List attributes) { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setAllowedToProxy(true); + r.setAnonymousAccess(false); + r.setEnabled(true); + r.setSsoEnabled(true); + r.setAllowedAttributes(attributes); + + if (attributes == null || attributes.isEmpty()) { + r.setIgnoreAttributes(true); + } + + return r; + } +} 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 0000000..1aa4dda --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/InMemoryServiceRegistryDaoImpl.java @@ -0,0 +1,87 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public final class InMemoryServiceRegistryDaoImpl implements ServiceRegistryDao { + + @NotNull + private List registeredServices = new ArrayList(); + + public boolean delete(RegisteredService registeredService) { + return this.registeredServices.remove(registeredService); + } + + public RegisteredService findServiceById(final long id) { + for (final RegisteredService r : this.registeredServices) { + if (r.getId() == id) { + return r; + } + } + + return null; + } + + public List load() { + return this.registeredServices; + } + + public RegisteredService save(final RegisteredService registeredService) { + if (registeredService.getId() == -1) { + ((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 I don't expect thousands of services. + * + * @return + */ + 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/JpaServiceRegistryDaoImpl.java b/cas-server-core/src/main/java/org/jasig/cas/services/JpaServiceRegistryDaoImpl.java new file mode 100644 index 0000000..8b4d697 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/JpaServiceRegistryDaoImpl.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import org.springframework.orm.jpa.support.JpaDaoSupport; + +import java.util.List; + +/** + * Implementation of the ServiceRegistryDao based on JPA. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class JpaServiceRegistryDaoImpl extends JpaDaoSupport implements + ServiceRegistryDao { + + public boolean delete(final RegisteredService registeredService) { + getJpaTemplate().remove( + getJpaTemplate().contains(registeredService) ? registeredService + : getJpaTemplate().merge(registeredService)); + return true; + } + + public List load() { + return getJpaTemplate().find("select r from AbstractRegisteredService r"); + } + + public RegisteredService save(final RegisteredService registeredService) { + final boolean isNew = registeredService.getId() == -1; + + final RegisteredService r = getJpaTemplate().merge(registeredService); + + if (!isNew) { + getJpaTemplate().persist(r); + } + + return r; + } + + public RegisteredService findServiceById(final long id) { + return getJpaTemplate().find(AbstractRegisteredService.class, id); + } +} 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 0000000..35fb06e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegexRegisteredService.java @@ -0,0 +1,63 @@ +/* + * 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. + */ +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. + * + * @author Marvin S. Addison + * @version $Revision: $ + */ +@Entity +@DiscriminatorValue("regex") +public class RegexRegisteredService extends AbstractRegisteredService { + /** Serialization version marker */ + private static final long serialVersionUID = -8258660210826975771L; + + private transient Pattern servicePattern; + + public void setServiceId(final String id) { + servicePattern = createPattern(id); + serviceId = id; + } + + public boolean matches(final Service service) { + if (servicePattern == null) { + servicePattern = createPattern(serviceId); + } + return service != null && servicePattern.matcher(service.getId()).matches(); + } + + protected AbstractRegisteredService newInstance() { + return new RegexRegisteredService(); + } + + private Pattern createPattern(final String pattern) { + if (pattern == null) { + throw new IllegalArgumentException("Pattern cannot be null."); + } + return Pattern.compile(pattern); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredService.java b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredService.java new file mode 100644 index 0000000..5dfac45 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredService.java @@ -0,0 +1,151 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import java.io.Serializable; +import java.util.List; + +import org.jasig.cas.authentication.principal.Service; + +/** + * Interface for a service that can be registered by the Services Management + * interface. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public interface RegisteredService extends Cloneable, Serializable { + /** + * Is this application currently allowed to use CAS? + * + * @return true if it can use CAS, false otherwise. + */ + boolean isEnabled(); + + /** + * Determines whether the service is allowed anonymous or privileged access + * to user information. Anonymous access should not return any identifying + * information such as user id. + * + * @return if we should use a pseudo random identifier instead of their real id + */ + boolean isAnonymousAccess(); + + /** + * Sets whether we should bother to read the attribute list or not. + * + * @return true if we should read it, false otherwise. + */ + boolean isIgnoreAttributes(); + + /** + * Returns the list of allowed attributes. + * + * @return the list of attributes + */ + List getAllowedAttributes(); + + /** + * Is this application allowed to take part in the proxying capabilities of + * CAS? + * + * @return true if it can, false otherwise. + */ + boolean isAllowedToProxy(); + + /** + * The unique identifier for this service. + * + * @return the unique identifier for this service. + */ + String getServiceId(); + + /** + * The numeric identifier for this service. + * + * @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(); + + /** + * Does this application participate in the SSO session? + * + * @return true if it does, false otherwise. + */ + boolean isSsoEnabled(); + + /** + * 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. + */ + void setEvaluationOrder(final int evaluationOrder); + + /** + * Get the name of the attribute this service prefers to consume as username. + * + * @return Either of the following values: + *

    + *
  • String representing the name of the attribute to consume as username
  • + *
  • null indicating the default username
  • + *
+ */ + public String getUsernameAttribute(); + + /** + * 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(final Service service); + + Object clone() throws CloneNotSupportedException; +} 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 0000000..20b81f1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/RegisteredServiceImpl.java @@ -0,0 +1,57 @@ +/* + * 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. + */ +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; + +/** + * Mutable registered service that uses Ant path patterns for service matching. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @version $Revision$ $Date$ + * @since 3.1 + */ +@Entity +@DiscriminatorValue("ant") +public class RegisteredServiceImpl extends AbstractRegisteredService { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -5906102762271197627L; + + private static final PathMatcher PATH_MATCHER = new AntPathMatcher(); + + public void setServiceId(final String id) { + this.serviceId = id; + } + + public boolean matches(final Service service) { + return service != null && PATH_MATCHER.match(serviceId.toLowerCase(), service.getId().toLowerCase()); + } + + protected AbstractRegisteredService newInstance() { + return new RegisteredServiceImpl(); + } +} + diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/ReloadableServicesManager.java b/cas-server-core/src/main/java/org/jasig/cas/services/ReloadableServicesManager.java new file mode 100644 index 0000000..cc47aac --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ReloadableServicesManager.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +/** + * Interface to allow for ServicesManagers to attempt to reload their list of + * services. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @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/src/main/java/org/jasig/cas/services/ServiceRegistryDao.java b/cas-server-core/src/main/java/org/jasig/cas/services/ServiceRegistryDao.java new file mode 100644 index 0000000..bb6dfa5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ServiceRegistryDao.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import java.util.List; + +/** + * Registry of all RegisteredServices. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @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(); + + RegisteredService findServiceById(long id); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/ServicesManager.java b/cas-server-core/src/main/java/org/jasig/cas/services/ServicesManager.java new file mode 100644 index 0000000..d32ecfd --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/ServicesManager.java @@ -0,0 +1,81 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @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/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 0000000..a89bb71 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedProxyingException.java @@ -0,0 +1,49 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +/** + * Exception thrown when a service attempts to proxy when it is not allowed to. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class UnauthorizedProxyingException extends UnauthorizedServiceException { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = -7307803750894078575L; + + /** The code description. */ + private static final String CODE = "service.not.authorized.proxy"; + + public UnauthorizedProxyingException() { + super(CODE); + } + + public UnauthorizedProxyingException(String message, Throwable cause) { + super(message, cause); + } + + public UnauthorizedProxyingException(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 0000000..63c5853 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedServiceException.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +/** + * Exception that is thrown when an Unauthorized Service attempts to use CAS. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class UnauthorizedServiceException extends RuntimeException { + + /** The Unique ID for serialization. */ + private static final long serialVersionUID = 3905807495715960369L; + + /** The code description. */ + private static final String CODE = "service.not.authorized"; + + public UnauthorizedServiceException() { + this(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); + } + + /** + * Constructs an exception with a custom message. + * + * @param message an explanatory message. + */ + public UnauthorizedServiceException(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 0000000..49c327c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/UnauthorizedSsoServiceException.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +/** + * Exception thrown when a service attempts to use SSO when it should not be + * allowed to. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @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"; + + public UnauthorizedSsoServiceException() { + this(CODE); + } + + public UnauthorizedSsoServiceException(final String message, + final Throwable cause) { + super(message, cause); + } + + 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 0000000..7d60115 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/AbstractServicesManagerMBean.java @@ -0,0 +1,98 @@ +/* + * 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. + */ + +package org.jasig.cas.services.jmx; + +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +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 + * @version $Revision$ $Date$ + * @since 3.4.4 + */ +public abstract class AbstractServicesManagerMBean { + + @NotNull + private T servicesManager; + + protected AbstractServicesManagerMBean(final T servicesManager) { + this.servicesManager = servicesManager; + } + + protected final T getServicesManager() { + return this.servicesManager; + } + + @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(" enabled: ").append(r.isEnabled()) + .append(" ssoEnabled: ").append(r.isSsoEnabled()) + .append(" serviceId: ").append(r.getServiceId()) + .toString()); + } + + return services; + } + + @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); + } + + @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); + } + + @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); + } + + 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. + ((RegisteredServiceImpl) r).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 0000000..cf30962 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ReloadableServicesManagerMBean.java @@ -0,0 +1,48 @@ +/* + * 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. + */ + +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 + * @version $Revision$ $Date$ + * @since 3.4.4 + */ +@ManagedResource(objectName = "CAS:name=JasigCasServicesManagerMBean", + description = "Exposes the services management tool via JMX", log = true, logFile="jasig_cas_jmx.log", + currencyTimeLimit = 15) +public final class ReloadableServicesManagerMBean extends AbstractServicesManagerMBean { + + public ReloadableServicesManagerMBean(final ReloadableServicesManager reloadableServicesManager) { + super(reloadableServicesManager); + } + + @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 0000000..e521f30 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/jmx/ServicesManagerMBean.java @@ -0,0 +1,41 @@ +/* + * 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. + */ + +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 + * @version $Revision$ $Date$ + * @since 3.4.4 + */ + +@ManagedResource(objectName = "CAS:name=JasigCasServicesManagerMBean", + description = "Exposes the services management tool via JMX", log = true, logFile="jasig_cas_jmx.log", + currencyTimeLimit = 15) +public final class ServicesManagerMBean extends AbstractServicesManagerMBean { + + public ServicesManagerMBean(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 0000000..6dd732a --- /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/web/ManageRegisteredServicesMultiActionController.java b/cas-server-core/src/main/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionController.java new file mode 100644 index 0000000..ef1c1b3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionController.java @@ -0,0 +1,143 @@ +/* + * 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. + */ +package org.jasig.cas.services.web; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.apache.commons.lang.math.NumberUtils; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.multiaction.MultiActionController; +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 + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class ManageRegisteredServicesMultiActionController extends MultiActionController { + + /** View name for the Manage Services View. */ + private static final String VIEW_NAME = "manageServiceView"; + + /** Instance of ServicesManager. */ + @NotNull + private final ServicesManager servicesManager; + + @NotNull + private final String defaultServiceUrl; + + /** + * Constructor that takes the required {@link ServicesManager}. + * + * @param servicesManager the Services Manager that manages the + * RegisteredServices. + * @param defaultServiceUrl the service management tool's url. + */ + public ManageRegisteredServicesMultiActionController( + final ServicesManager servicesManager, final String defaultServiceUrl) { + super(); + this.servicesManager = servicesManager; + this.defaultServiceUrl = defaultServiceUrl; + } + + /** + * Method to delete the RegisteredService by its ID. + * + * @param request the HttpServletRequest + * @param response the HttpServletResponse + * @return the Model and View to go to after the service is deleted. + */ + public ModelAndView deleteRegisteredService( + final HttpServletRequest request, final HttpServletResponse response) { + final String id = request.getParameter("id"); + final long idAsLong = Long.parseLong(id); + + 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. + * + * @param request the HttpServletRequest + * @param response the HttpServletResponse + * @return the Model and View to go to after the services are loaded. + */ + public ModelAndView manage(final HttpServletRequest request, + final HttpServletResponse response) { + final Map model = new HashMap(); + + final List services = new ArrayList(this.servicesManager.getAllServices()); + + model.put("services", services); + model.put("pageTitle", VIEW_NAME); + model.put("defaultServiceUrl", this.defaultServiceUrl); + + return new ModelAndView(VIEW_NAME, 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. + * + * @param request The request object that is expected to contain the id and evaluationOrder + * as parameters. + * @param response The response object. + * + * @returns {@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 unsuccesful. + * There will also be a successful boolean parameter that indicates whether or not the update + * was successful. + * + * @throws IllegalArgumentException If either of the id or evaluationOrder are invalid + * or if the service cannot be located for that id by the active implementation of the {@link ServicesManager}. + */ + public ModelAndView updateRegisteredServiceEvaluationOrder(final HttpServletRequest request, final HttpServletResponse response) { + long id = Long.parseLong(request.getParameter("id")); + int evaluationOrder = Integer.parseInt(request.getParameter("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-server-core/src/main/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormController.java b/cas-server-core/src/main/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormController.java new file mode 100644 index 0000000..b587ac6 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormController.java @@ -0,0 +1,169 @@ +/* + * 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. + */ +package org.jasig.cas.services.web; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +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.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.util.StringUtils; +import org.springframework.validation.BindException; +import org.springframework.web.bind.ServletRequestDataBinder; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.SimpleFormController; +import org.springframework.web.servlet.view.RedirectView; + +/** + * SimpleFormController to handle adding/editing of RegisteredServices. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class RegisteredServiceSimpleFormController extends SimpleFormController { + + /** Instance of ServiceRegistryManager */ + @NotNull + private final ServicesManager servicesManager; + + /** Instance of AttributeRegistry. */ + @NotNull + private final IPersonAttributeDao personAttributeDao; + + public RegisteredServiceSimpleFormController( + final ServicesManager servicesManager, + final IPersonAttributeDao attributeRepository) { + this.servicesManager = servicesManager; + this.personAttributeDao = attributeRepository; + } + + /** + * Sets the require fields and the disallowed fields from the + * HttpServletRequest. + * + * @see org.springframework.web.servlet.mvc.BaseCommandController#initBinder(javax.servlet.http.HttpServletRequest, + * org.springframework.web.bind.ServletRequestDataBinder) + */ + protected final void initBinder(final HttpServletRequest request, + final ServletRequestDataBinder binder) throws Exception { + binder.setRequiredFields(new String[] {"description", "serviceId", + "name", "allowedToProxy", "enabled", "ssoEnabled", + "anonymousAccess", "evaluationOrder"}); + binder.setDisallowedFields(new String[] {"id"}); + binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); + } + + /** + * Adds the service to the ServiceRegistry via the ServiceRegistryManager. + * + * @see org.springframework.web.servlet.mvc.SimpleFormController#onSubmit(javax.servlet.http.HttpServletRequest, + * javax.servlet.http.HttpServletResponse, java.lang.Object, + * org.springframework.validation.BindException) + */ + protected final ModelAndView onSubmit(final HttpServletRequest request, + final HttpServletResponse response, final Object command, + final BindException errors) throws Exception { + RegisteredService service = (RegisteredService) command; + + // only change object class if there isn't an explicit RegisteredService class set + if (this.getCommandClass() == null) { + // CAS-1071 + // Treat _new_ patterns starting with ^ character as a regular expression + if (service.getId() < 0 && service.getServiceId().startsWith("^")) { + logger.debug("Detected regular expression starting with ^"); + final RegexRegisteredService regexService = new RegexRegisteredService(); + regexService.copyFrom(service); + service = regexService; + } + } + this.servicesManager.save(service); + logger.info("Saved changes to service " + service.getId()); + + final ModelAndView modelAndView = new ModelAndView(new RedirectView( + "manage.html#" + service.getId(), true)); + modelAndView.addObject("action", "add"); + modelAndView.addObject("id", service.getId()); + + return modelAndView; + } + + protected Object formBackingObject(final HttpServletRequest request) + throws Exception { + final String id = request.getParameter("id"); + + if (!StringUtils.hasText(id)) { + // create a default RegisteredServiceImpl object if an explicit class isn't set + final Object service; + if (this.getCommandClass() != null) { + service = this.createCommand(); + } else { + 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; + } + + /** + * Returns the attributes, page title, and command name. + * + * @see org.springframework.web.servlet.mvc.SimpleFormController#referenceData(javax.servlet.http.HttpServletRequest) + */ + protected final Map referenceData(final HttpServletRequest request) throws Exception { + + final Map model = new HashMap(); + + final List possibleAttributeNames = new ArrayList(); + possibleAttributeNames.addAll(this.personAttributeDao.getPossibleUserAttributeNames()); + Collections.sort(possibleAttributeNames); + model.put("availableAttributes", possibleAttributeNames); + + final List possibleUsernameAttributeNames = new ArrayList(); + possibleUsernameAttributeNames.addAll(possibleAttributeNames); + possibleUsernameAttributeNames.add(0, ""); + model.put("availableUsernameAttributes", possibleUsernameAttributeNames); + + + model.put("pageTitle", getFormView()); + model.put("commandName", getCommandName()); + return model; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/web/ServiceThemeResolver.java b/cas-server-core/src/main/java/org/jasig/cas/services/web/ServiceThemeResolver.java new file mode 100644 index 0000000..9c14c7c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/web/ServiceThemeResolver.java @@ -0,0 +1,109 @@ +/* + * 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. + */ +package org.jasig.cas.services.web; + +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.ArgumentExtractor; +import org.jasig.cas.web.support.WebUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.theme.AbstractThemeResolver; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.*; +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class ServiceThemeResolver extends AbstractThemeResolver { + + /** The ServiceRegistry to look up the service. */ + private ServicesManager servicesManager; + + private List argumentExtractors; + + private Map overrides = new HashMap(); + + public String resolveThemeName(final HttpServletRequest request) { + if (this.servicesManager == null) { + return getDefaultThemeName(); + } + + final Service service = WebUtils.getService(this.argumentExtractors, request); + + final RegisteredService rService = this.servicesManager.findServiceBy(service); + + // retrieve the user agent string from the request + String userAgent = request.getHeader("User-Agent"); + + if (userAgent == null) { + 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; + } + } + + return service != null && rService != null && StringUtils.hasText(rService.getTheme()) ? rService.getTheme() : getDefaultThemeName(); + } + + 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; + } + + public void setArgumentExtractors(final List argumentExtractors) { + this.argumentExtractors = argumentExtractors; + } + + /** + * 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()); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/web/package.html b/cas-server-core/src/main/java/org/jasig/cas/services/web/package.html new file mode 100644 index 0000000..5193fdc --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/web/package.html @@ -0,0 +1,27 @@ + + + +

This package contains all the Service Whitelist/customization classes +related to the web tier, such as Theme Resolvers that match a theme +(skin) for the login pages to a registered service.

+ + diff --git a/cas-server-core/src/main/java/org/jasig/cas/services/web/support/RegisteredServiceValidator.java b/cas-server-core/src/main/java/org/jasig/cas/services/web/support/RegisteredServiceValidator.java new file mode 100644 index 0000000..4d99c9b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/services/web/support/RegisteredServiceValidator.java @@ -0,0 +1,114 @@ +/* + * 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. + */ +package org.jasig.cas.services.web.support; + +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.services.persondir.IPersonAttributeDao; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +/** + * RegisteredServiceValidator ensures that a new RegisteredService does not have + * a conflicting Service Id with another service already in the registry. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +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 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 IPersonAttributeDao personAttributeDao; + + /** + * Supports {@link RegisteredService} objects. + * + * @see org.springframework.validation.Validator#supports(java.lang.Class) + */ + public boolean supports(final Class clazz) { + return RegisteredService.class.isAssignableFrom(clazz); + } + + 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); + } + + if (!StringUtils.isBlank(r.getUsernameAttribute()) && !r.isAnonymousAccess()) { + if (!r.isIgnoreAttributes() && !r.getAllowedAttributes().contains(r.getUsernameAttribute())) { + errors.rejectValue("usernameAttribute", "registeredService.usernameAttribute.notAvailable", + "This attribute is not available for this service."); + } else { + Set availableAttributes = this.personAttributeDao.getPossibleUserAttributeNames(); + if (availableAttributes != null) { + if (!availableAttributes.contains(r.getUsernameAttribute())) { + errors.rejectValue("usernameAttribute", "registeredService.usernameAttribute.notAvailable", + "This attribute is not available from configured user attribute sources."); + } + } + } + } + } + + public void setServicesManager(final ServicesManager serviceRegistry) { + this.servicesManager = serviceRegistry; + } + + public void setMaxDescriptionLength(final int maxLength) { + this.maxDescriptionLength = maxLength; + } + + public void setPersonAttributeDao(IPersonAttributeDao personAttributeDao) { + this.personAttributeDao = personAttributeDao; + } +} 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 0000000..422c149 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/AbstractTicket.java @@ -0,0 +1,145 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import javax.persistence.Column; +import javax.persistence.Id; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; + +import org.springframework.util.Assert; + +/** + * 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). + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +@MappedSuperclass +public abstract class AbstractTicket implements Ticket, TicketState { + + /** The ExpirationPolicy this ticket will be following. */ + // XXX removed final + @Lob + @Column(name="EXPIRATION_POLICY", 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 + private TicketGrantingTicketImpl 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; + + 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 TicketGrantingTicketImpl 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; + } + + 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; + } + + public final int hashCode() { + return 34 ^ this.getId().hashCode(); + } + + public final String toString() { + return this.id; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/ExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/ExpirationPolicy.java new file mode 100644 index 0000000..5320a99 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/ExpirationPolicy.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ * @see org.jasig.cas.ticket.Ticket + */ +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/src/main/java/org/jasig/cas/ticket/InvalidTicketException.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/InvalidTicketException.java new file mode 100644 index 0000000..ea4fa0d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/InvalidTicketException.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +/** + * TicketException to alert that a Ticket was not found or that it is expired. + * + * @author Scott Battaglia + * @version $Revison$ $Date$ + * @since 3.0 + */ +public class InvalidTicketException extends TicketException { + + /** The Unique Serializable ID. */ + private static final long serialVersionUID = 3256723974594508849L; + + /** The code description. */ + private static final String CODE = "INVALID_TICKET"; + + /** + * Constructs a InvalidTicketException with the default exception code. + */ + public InvalidTicketException() { + super(CODE); + } + + /** + * Constructs a InvalidTicketException with the default exception code and + * the original exception that was thrown. + * + * @param throwable the chained exception + */ + public InvalidTicketException(final Throwable throwable) { + super(CODE, throwable); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicket.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicket.java new file mode 100644 index 0000000..e26dd74 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicket.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public interface ServiceTicket extends Ticket { + + /** Prefix generally applied to unique ids generated by UniqueIdGenenerator. */ + String PREFIX = "ST"; + + /** + * 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(); + + 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. + * @return The ticket granting ticket. + */ + TicketGrantingTicket grantTicketGrantingTicket(String id, + Authentication authentication, ExpirationPolicy expirationPolicy); +} 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 0000000..2ef415b --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/ServiceTicketImpl.java @@ -0,0 +1,131 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.Table; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.springframework.util.Assert; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.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 = false; + + 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, + final TicketGrantingTicketImpl ticket, final Service service, + final boolean fromNewLogin, final ExpirationPolicy policy) { + super(id, ticket, policy); + + Assert.notNull(ticket, "ticket cannot be null"); + Assert.notNull(service, "service cannot be null"); + + this.service = service; + this.fromNewLogin = fromNewLogin; + } + + public boolean isFromNewLogin() { + return this.fromNewLogin; + } + + public Service getService() { + return this.service; + } + + public boolean isValidFor(final Service serviceToValidate) { + updateState(); + return serviceToValidate.matches(this.service); + } + + 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 = true; + } + + return new TicketGrantingTicketImpl(id, (TicketGrantingTicketImpl) this.getGrantingTicket(), + authentication, expirationPolicy); + } + + public Authentication getAuthentication() { + return null; + } + + public final boolean equals(final Object object) { + if (object == null + || !(object instanceof ServiceTicket)) { + return false; + } + + final Ticket serviceTicket = (Ticket) object; + + return serviceTicket.getId().equals(this.getId()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/Ticket.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/Ticket.java new file mode 100644 index 0000000..bf2cba9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/Ticket.java @@ -0,0 +1,66 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import java.io.Serializable; + +/** + * Interface for the generic concept of a ticket. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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. + * + * @see org.jasig.cas.ticket.ExpirationPolicy + */ + boolean isExpired(); + + /** + * Method to retrive 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(); + + /** + * Returns the number of times this ticket was used. + * @return + */ + int getCountOfUses(); +} 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 0000000..9ecc1ba --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketCreationException.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +/** + * Exception thrown if there is an error creating a ticket. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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/TicketException.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketException.java new file mode 100644 index 0000000..8a17978 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketException.java @@ -0,0 +1,66 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +/** + * Generic ticket exception. Top of the TicketException heirarchy. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public abstract class TicketException extends Exception { + + /** Serializable Unique ID. */ + private static final long serialVersionUID = -6000583436059919480L; + + /** The code description of the TicketException. */ + private String code; + + /** + * Constructs a new TicketException with the code identifying the exception + * type. + * + * @param code the code to describe what type of exception this is. + */ + public TicketException(final String code) { + this.code = code; + } + + /** + * Constructs a new TicketException 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 TicketException(final String code, final Throwable throwable) { + super(throwable); + this.code = code; + } + + /** + * @return Returns the code. If there is a chained exception it returns the + * toString-ed version of the chained exception rather than the code. + */ + public final String getCode() { + return (this.getCause() != null) ? this.getCause().toString() + : this.code; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicket.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicket.java new file mode 100644 index 0000000..879e670 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicket.java @@ -0,0 +1,80 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import java.util.List; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public interface TicketGrantingTicket extends Ticket { + + /** The prefix to use when generating an id for a TicketGrantingTicket. */ + String PREFIX = "TGT"; + + /** + * Method to retrieve the authentication. + * + * @return the authentication + */ + Authentication getAuthentication(); + + /** + * 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 + * @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); + + /** + * Explicitly expire a ticket. This method will log out of any service associated with the + * Ticket Granting Ticket. + * + */ + void expire(); + + /** + * 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(); + + /** + * Method to retrieve the chained list of Authentications for this + * TicketGrantingTicket. + * + * @return the list of principals + */ + List getChainedAuthentications(); +} 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 0000000..9ba6aaf --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketGrantingTicketImpl.java @@ -0,0 +1,175 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Lob; +import javax.persistence.Table; + +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; + +/** + * 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 + * @version $Revision: 1.3 $ $Date: 2007/02/20 14:41:04 $ + * @since 3.0 + */ +@Entity +@Table(name="TICKETGRANTINGTICKET") +public final class TicketGrantingTicketImpl extends AbstractTicket implements + TicketGrantingTicket { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -5197946718924166491L; + + private static final Logger LOG = LoggerFactory.getLogger(TicketGrantingTicketImpl.class); + + /** The authenticated object for which this ticket was generated for. */ + @Lob + @Column(name="AUTHENTICATION", nullable=false) + private Authentication authentication; + + /** Flag to enforce manual expiration. */ + @Column(name="EXPIRED", nullable=false) + private Boolean expired = false; + + @Lob + @Column(name="SERVICES_GRANTED_ACCESS_TO", nullable=false) + private final HashMap services = new HashMap(); + + public TicketGrantingTicketImpl() { + // nothing to do + } + + /** + * Constructs a new TicketGrantingTicket. + * + * @param id the id of the Ticket + * @param ticketGrantingTicket the parent ticket + * @param authentication the Authentication request for this ticket + * @param policy the expiration policy for this ticket. + * @throws IllegalArgumentException if the Authentication object is null + */ + public TicketGrantingTicketImpl(final String id, + final TicketGrantingTicketImpl ticketGrantingTicket, + final Authentication authentication, final ExpirationPolicy policy) { + super(id, ticketGrantingTicket, policy); + + Assert.notNull(authentication, "authentication cannot be null"); + + this.authentication = authentication; + } + + /** + * 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, authentication, policy); + } + + public Authentication getAuthentication() { + return this.authentication; + } + + 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; + } + + private void logOutOfServices() { + for (final Entry entry : this.services.entrySet()) { + + if (!entry.getValue().logOutOfService(entry.getKey())) { + LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing..."); + } + } + } + + public boolean isRoot() { + return this.getGrantingTicket() == null; + } + + public synchronized void expire() { + this.expired = true; + logOutOfServices(); + } + + public boolean isExpiredInternal() { + return this.expired; + } + + public List getChainedAuthentications() { + final List list = new ArrayList(); + + if (this.getGrantingTicket() == null) { + list.add(this.getAuthentication()); + return Collections.unmodifiableList(list); + } + + list.add(this.getAuthentication()); + list.addAll(this.getGrantingTicket().getChainedAuthentications()); + + return Collections.unmodifiableList(list); + } + + public final boolean equals(final Object object) { + if (object == null + || !(object instanceof TicketGrantingTicket)) { + return false; + } + + final Ticket ticket = (Ticket) object; + + return ticket.getId().equals(this.getId()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketState.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketState.java new file mode 100644 index 0000000..c940161 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketState.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.Authentication; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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/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 0000000..55971c2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/TicketValidationException.java @@ -0,0 +1,55 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class TicketValidationException extends TicketException { + + /** Unique Serial ID. */ + private static final long serialVersionUID = 3257004341537093175L; + + /** The code description. */ + private static final String CODE = "INVALID_SERVICE"; + + private final Service service; + + /** + * Constructs a TicketValidationException with the default exception code + * and the original exception that was thrown. + * + * @param throwable the chained exception + */ + public TicketValidationException(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/package.html b/cas-server-core/src/main/java/org/jasig/cas/ticket/package.html new file mode 100644 index 0000000..3cd8d41 --- /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/ProxyHandler.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/ProxyHandler.java new file mode 100644 index 0000000..19a5dbb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/ProxyHandler.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.proxy; + +import org.jasig.cas.authentication.principal.Credentials; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface ProxyHandler { + + /** + * Method to actually process the proxy request. + * + * @param credentials The credentials of the item that will be proxying. + * @param proxyGrantingTicketId The ticketId for the ProxyGrantingTicket (in + * CAS 3 this is a TicketGrantingTicket) + * @return the String value that needs to be passed to the CAS client. + */ + String handle(Credentials credentials, String proxyGrantingTicketId); +} 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 0000000..ed8dc49 --- /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 0000000..04319d1 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandler.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.authentication.principal.Credentials; +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class Cas10ProxyHandler implements ProxyHandler { + + public String handle(final Credentials credentials, + final String proxyGrantingTicketId) { + return null; + } + +} 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 0000000..fc97210 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandler.java @@ -0,0 +1,108 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.HttpBasedServiceCredentials; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.HttpClient; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.validation.constraints.NotNull; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class Cas20ProxyHandler implements ProxyHandler { + + /** The Commons Logging instance. */ + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** The PGTIOU ticket prefix. */ + private static final String PGTIOU_PREFIX = "PGTIOU"; + + /** Generate unique ids. */ + @NotNull + private UniqueTicketIdGenerator uniqueTicketIdGenerator = new DefaultUniqueTicketIdGenerator(); + + /** Instance of Apache Commons HttpClient */ + @NotNull + private HttpClient httpClient; + + public String handle(final Credentials credentials, + final String proxyGrantingTicketId) { + final HttpBasedServiceCredentials serviceCredentials = (HttpBasedServiceCredentials) credentials; + final String proxyIou = this.uniqueTicketIdGenerator + .getNewTicketId(PGTIOU_PREFIX); + final String serviceCredentialsAsString = serviceCredentials.getCallbackUrl().toExternalForm(); + final StringBuilder stringBuffer = new StringBuilder( + serviceCredentialsAsString.length() + proxyIou.length() + + proxyGrantingTicketId.length() + 15); + + stringBuffer.append(serviceCredentialsAsString); + + if (serviceCredentials.getCallbackUrl().getQuery() != null) { + stringBuffer.append("&"); + } else { + stringBuffer.append("?"); + } + + stringBuffer.append("pgtIou="); + stringBuffer.append(proxyIou); + stringBuffer.append("&pgtId="); + stringBuffer.append(proxyGrantingTicketId); + + if (this.httpClient.isValidEndPoint(stringBuffer.toString())) { + if (log.isDebugEnabled()) { + log.debug("Sent ProxyIou of " + proxyIou + " for service: " + + serviceCredentials.toString()); + } + return proxyIou; + } + + if (log.isDebugEnabled()) { + log.debug("Failed to send ProxyIou of " + proxyIou + + " for service: " + serviceCredentials.toString()); + } + 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; + } +} 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 0000000..615de89 --- /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 0000000..bcc531d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractDistributedTicketRegistry.java @@ -0,0 +1,184 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry; + +import java.util.List; + +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; + +/** + * Abstract Implementation that handles some of the commonalities between + * distributed ticket registries. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractDistributedTicketRegistry extends AbstractTicketRegistry { + + protected abstract void updateTicket(final Ticket ticket); + + protected abstract boolean needsCallback(); + + 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; + + protected TicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, final T ticket, final boolean callback) { + this.ticketRegistry = ticketRegistry; + this.ticket = ticket; + this.callback = callback; + } + + protected void updateTicket() { + this.ticketRegistry.updateTicket(this.ticket); + } + + protected T getTicket() { + return this.ticket; + } + + public final String getId() { + return this.ticket.getId(); + } + + public final boolean isExpired() { + if (!callback) { + return this.ticket.isExpired(); + } + + final TicketGrantingTicket t = getGrantingTicket(); + + return this.ticket.isExpired() || (t != null && t.isExpired()); + } + + public final TicketGrantingTicket getGrantingTicket() { + final TicketGrantingTicket old = this.ticket.getGrantingTicket(); + + if (old == null || !callback) { + return old; + } + + return (TicketGrantingTicket) 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; + + protected ServiceTicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, final ServiceTicket serviceTicket, final boolean callback) { + super(ticketRegistry, serviceTicket, callback); + } + + + public Service getService() { + return getTicket().getService(); + } + + public boolean isFromNewLogin() { + return getTicket().isFromNewLogin(); + } + + public boolean isValidFor(final Service service) { + final boolean b = this.getTicket().isValidFor(service); + updateTicket(); + return b; + } + + 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 = 3946038899057626741L; + + protected TicketGrantingTicketDelegator(final AbstractDistributedTicketRegistry ticketRegistry, final TicketGrantingTicket ticketGrantingTicket, final boolean callback) { + super(ticketRegistry, ticketGrantingTicket, callback); + } + + public Authentication getAuthentication() { + return getTicket().getAuthentication(); + } + + 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; + } + + public void expire() { + this.getTicket().expire(); + updateTicket(); + } + + public boolean isRoot() { + return getTicket().isRoot(); + } + + public List getChainedAuthentications() { + return getTicket().getChainedAuthentications(); + } + } +} 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 0000000..c8461f3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/AbstractTicketRegistry.java @@ -0,0 +1,74 @@ +/* + * 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. + */ +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.4 + *

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

+ */ +public abstract class AbstractTicketRegistry implements TicketRegistry, TicketRegistryState { + + /** The Commons Logging log instance. */ + protected final Logger log = LoggerFactory.getLogger(getClass()); + + /** + * @throws IllegalArgumentException if class is null. + * @throws ClassCastException if class does not match requested ticket + * class. + */ + public final Ticket 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 ticket; + } + + public int sessionCount() { + log.debug("sessionCount() operation is not implemented by the ticket registry instance {}. Returning unknown as {}", + this.getClass().getName(), Integer.MIN_VALUE); + return Integer.MIN_VALUE; + } + + public int serviceTicketCount() { + log.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 0000000..6746b05 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/DefaultTicketRegistry.java @@ -0,0 +1,127 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class DefaultTicketRegistry extends AbstractTicketRegistry { + + /** A HashMap to contain the tickets. */ + private final Map cache; + + 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(int initialCapacity, final float loadFactor, final int concurrencyLevel) { + this.cache = new ConcurrentHashMap(initialCapacity, loadFactor, concurrencyLevel); + } + + /** + * @throws IllegalArgumentException if the Ticket is null. + */ + public void addTicket(final Ticket ticket) { + Assert.notNull(ticket, "ticket cannot be null"); + + if (log.isDebugEnabled()) { + log.debug("Added ticket [" + ticket.getId() + "] to registry."); + } + this.cache.put(ticket.getId(), ticket); + } + + public Ticket getTicket(final String ticketId) { + if (ticketId == null) { + return null; + } + + if (log.isDebugEnabled()) { + log.debug("Attempting to retrieve ticket [" + ticketId + "]"); + } + final Ticket ticket = this.cache.get(ticketId); + + if (ticket != null) { + log.debug("Ticket [" + ticketId + "] found in registry."); + } + + return ticket; + } + + public boolean deleteTicket(final String ticketId) { + if (ticketId == null) { + return false; + } + if (log.isDebugEnabled()) { + log.debug("Removing ticket [" + ticketId + "] from registry"); + } + + return (this.cache.remove(ticketId) != null); + } + + public Collection getTickets() { + return Collections.unmodifiableCollection(this.cache.values()); + } + + public int sessionCount() { + int count = 0; + for (Ticket t : this.cache.values()) { + if (t instanceof TicketGrantingTicket) { + count++; + } + } + return count; + } + + public int serviceTicketCount() { + int count = 0; + for (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/JpaTicketRegistry.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/JpaTicketRegistry.java new file mode 100644 index 0000000..f37807d --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/JpaTicketRegistry.java @@ -0,0 +1,185 @@ +/* + * 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. + */ +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"; + + + protected void updateTicket(final Ticket ticket) { + entityManager.merge(ticket); + log.debug("Updated ticket [{}].", ticket); + } + + @Transactional(readOnly = false) + public void addTicket(final Ticket ticket) { + entityManager.persist(ticket); + log.debug("Added ticket [{}] to registry.", ticket); + } + + @Transactional(readOnly = false) + public boolean deleteTicket(final String ticketId) { + final Ticket ticket = getRawTicket(ticketId); + + if (ticket == null) { + return false; + } + + if (ticket instanceof ServiceTicket) { + removeTicket(ticket); + log.debug("Deleted ticket [{}] from the registry.", ticket); + return true; + } + + deleteTicketAndChildren(ticket); + log.debug("Deleted ticket [{}] and its children from the registry.", ticket); + return true; + } + + 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); + } + + private void removeTicket(final Ticket ticket) { + try { + if (log.isDebugEnabled()) { + final Date creationDate = new Date(ticket.getCreationTime()); + log.debug("Removing Ticket [{}] created: {}", ticket, creationDate.toString()); + } + entityManager.remove(ticket); + } catch (final Exception e) { + log.error("Error removing {} from registry.", ticket, e); + } + } + + @Transactional(readOnly=true) + public Ticket getTicket(final String ticketId) { + return getProxiedTicketInstance(getRawTicket(ticketId)); + } + + 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) { + log.error("Error getting ticket {} from registry.", ticketId, e); + } + return null; + } + + @Transactional(readOnly=true) + 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) + public int sessionCount() { + return countToInt(entityManager.createQuery("select count(t) from TicketGrantingTicketImpl t").getSingleResult()); + } + + @Transactional(readOnly=true) + public int serviceTicketCount() { + return countToInt(entityManager.createQuery("select count(t) from ServiceTicketImpl t").getSingleResult()); + } + + 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-core/src/main/java/org/jasig/cas/ticket/registry/RegistryCleaner.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/RegistryCleaner.java new file mode 100644 index 0000000..8915247 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/RegistryCleaner.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package org.jasig.cas.ticket.registry; + +/** + * Strategy interface to denote the start of cleaning the registry. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface RegistryCleaner { + + /** + * Method to kick-off the cleaning of a registry. + */ + void clean(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/TicketRegistry.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/TicketRegistry.java new file mode 100644 index 0000000..cedaac7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/TicketRegistry.java @@ -0,0 +1,85 @@ +/* + * 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. + */ + +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 + * @version $Revision$ $Date$ + * @since 3.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. + * @return the requested ticket. + * @throws InvalidTicketClassException if the ticket does not match the + * class provided. + */ + Ticket 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/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 0000000..a81dd60 --- /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 0000000..a78f2a9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleaner.java @@ -0,0 +1,146 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry.support; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +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; + +/** + * 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 + * {@link org.jasig.cas.ticket.registry.JpaTicketRegistry}, for example. + * By default this implementation uses {@link NoOpLockingStrategy} to preserve + * the same semantics as previous versions, but {@link JpaLockingStrategy} + * should be used with {@link org.jasig.cas.ticket.registry.JpaTicketRegistry} + * in a clustered CAS environment. + *

+ *

The following property is required.

+ *
    + *
  • ticketRegistry - CAS ticket registry.
  • + *
+ * + * @author Scott Battaglia + * @author Marvin S. Addison + * @version $Revision$ + * @since 3.0 + * @see JpaLockingStrategy + * @see NoOpLockingStrategy + */ +public final class DefaultTicketRegistryCleaner implements RegistryCleaner { + + /** The Commons Logging instance. */ + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** The instance of the TicketRegistry to clean. */ + @NotNull + private TicketRegistry ticketRegistry; + + /** Execution locking strategy */ + @NotNull + private LockingStrategy lock = new NoOpLockingStrategy(); + + private boolean logUserOutOfServices = true; + + + /** + * @see org.jasig.cas.ticket.registry.RegistryCleaner#clean() + */ + public void clean() { + this.log.info("Beginning ticket cleanup."); + this.log.debug("Attempting to acquire ticket cleanup lock."); + if (!this.lock.acquire()) { + this.log.info("Could not obtain lock. Aborting cleanup."); + return; + } + this.log.debug("Acquired lock. Proceeding with cleanup."); + try { + final List ticketsToRemove = new ArrayList(); + final Collection ticketsInCache; + ticketsInCache = this.ticketRegistry.getTickets(); + for (final Ticket ticket : ticketsInCache) { + if (ticket.isExpired()) { + ticketsToRemove.add(ticket); + } + } + + this.log.info(ticketsToRemove.size() + " tickets found to be removed."); + for (final Ticket ticket : ticketsToRemove) { + // CAS-686: Expire TGT to trigger single sign-out + if (this.logUserOutOfServices && ticket instanceof TicketGrantingTicket) { + ((TicketGrantingTicket) ticket).expire(); + } + this.ticketRegistry.deleteTicket(ticket.getId()); + } + } finally { + this.log.debug("Releasing ticket cleanup lock."); + this.lock.release(); + } + + this.log.info("Finished ticket cleanup."); + } + + + /** + * @param ticketRegistry The ticketRegistry to set. + */ + public void setTicketRegistry(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + } + + + /** + * @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 {@link JdbcLockingStrategy} + * for {@link org.jasig.cas.ticket.registry.JpaTicketRegistry} in a clustered + * CAS environment. + */ + public void setLock(final LockingStrategy strategy) { + this.lock = strategy; + } + + /** + * Whether to log users out of services when we remove an expired ticket. The default is true. Set this to + * false to disable. + * + * @param logUserOutOfServices whether to log the user out of services or not. + */ + public void setLogUserOutOfServices(final boolean logUserOutOfServices) { + this.logUserOutOfServices = logUserOutOfServices; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/JdbcLockingStrategy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/JdbcLockingStrategy.java new file mode 100644 index 0000000..a8a42cc --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/JdbcLockingStrategy.java @@ -0,0 +1,364 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry.support; + +import java.sql.Timestamp; +import java.util.Calendar; + +import javax.sql.DataSource; +import javax.validation.constraints.NotNull; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.SqlRowSetResultSetExtractor; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.transaction.annotation.Transactional; + +/** + * Locking strategy that uses database storage for lock state that has the + * following properties: + *
    + *
  • Exclusivity - Only only client at a time may acquire the lock.
  • + *
  • Non-reentrant - An attempt to re-acquire the lock by the current + * holder will fail.
  • + *
  • Lock expiration - Locks are acquired with an expiration such that + * a request to acquire the lock after the expiration date will succeed even + * if it is currently held by another client.
  • + *
+ *

+ * This class requires a backing database table to exist based on the + * following template: + *

+ * CREATE TABLE LOCKS (
+ *   APPLICATION_ID VARCHAR(50) NOT NULL,
+ *   UNIQUE_ID VARCHAR(50) NULL,
+ *   EXPIRATION_DATE TIMESTAMP NULL
+ * );
+ * ALTER TABLE LOCKS ADD CONSTRAINT LOCKS_PK
+ * PRIMARY KEY (APPLICATION_ID);
+ * 
+ *

+ *

+ * Note that table and column names can be controlled through instance + * properties, but the create table script above is consistent with defaults. + *

+ * + * @author Marvin S. Addison + * @version $Revision$ + * @since 3.3.6 + * @deprecated Replaced by {@link JpaLockingStrategy} as of 3.4.11. + * @see JpaLockingStrategy + * + */ +@Deprecated +public class JdbcLockingStrategy + implements LockingStrategy, InitializingBean { + + /** Default lock timeout is 1 hour */ + public static final int DEFAULT_LOCK_TIMEOUT = 3600; + + /** Default database platform is SQL-92 */ + private static final DatabasePlatform DEFAULT_PLATFORM = + DatabasePlatform.SQL92; + + /** Default locking table name is LOCKS */ + private static final String DEFAULT_TABLE_NAME = "LOCKS"; + + /** Default unique identifier column name is UNIQUE_ID */ + private static final String UNIQUE_ID_COLUMN_NAME = "UNIQUE_ID"; + + /** Default application identifier column name is APPLICATION_ID */ + private static final String APPLICATION_ID_COLUMN_NAME = "APPLICATION_ID"; + + /** Default expiration date column name is EXPIRATION_DATE */ + private static final String EXPIRATION_DATE_COLUMN_NAME = "EXPIRATION_DATE"; + + /** Database table name that stores locks */ + @NotNull + private String tableName = DEFAULT_TABLE_NAME; + + /** Database column name that holds unique identifier */ + @NotNull + private String uniqueIdColumnName = UNIQUE_ID_COLUMN_NAME; + + /** Database column name that holds application identifier */ + @NotNull + private String applicationIdColumnName = APPLICATION_ID_COLUMN_NAME; + + /** Database column name that holds expiration date */ + @NotNull + private String expirationDateColumnName = EXPIRATION_DATE_COLUMN_NAME; + + /** Unique identifier that identifies the client using this lock instance */ + @NotNull + private String uniqueId; + + /** + * 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; + + /** Amount of time in seconds lock may be held */ + private int lockTimeout = DEFAULT_LOCK_TIMEOUT; + + /** JDBC data source */ + @NotNull + private DataSource dataSource; + + /** Database platform */ + @NotNull + private DatabasePlatform platform = DEFAULT_PLATFORM; + + /** Spring JDBC template used to execute SQL statements */ + private JdbcTemplate jdbcTemplate; + + /** SQL statement for selecting a lock */ + private String selectSql; + + /** SQL statement for creating a lock for a given application ID */ + private String createSql; + + /** SQL statement for updating a lock to acquired state */ + private String updateAcquireSql; + + /** SQL statement for updating a lock to released state */ + private String updateReleaseSql; + + + /** + * Supported database platforms provides support for platform-specific + * behavior such as locking semantics. + */ + public enum DatabasePlatform { + /** + * Any platform that supports the SQL-92 FOR UPDATE updatability clause + * for SELECT queries and the related exclusive row locking + * semantics it suggests. + */ + SQL92, + + /** HSQLDB platform */ + HSQL, + + /** Microsoft SQL Server platform */ + SqlServer; + } + + + /** + * @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 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 seconds Maximum amount of time in seconds lock may be held. + */ + public void setLockTimeout(final int seconds) { + this.lockTimeout = seconds; + } + + + /** + * @param name Name of database table holding locks. + */ + public void setTableName(final String name) { + this.tableName = name; + } + + + /** + * @param name Name of database column that stores application ID. + */ + public void setApplicationIdColumnName(final String name) { + this.applicationIdColumnName = name; + } + + + /** + * @param name Name of database column that stores unique ID. + */ + public void setUniqueIdColumnName(final String name) { + this.uniqueIdColumnName = name; + } + + + /** + * @param name Name of database column that stores lock expiration date. + */ + public void setExpirationDateColumnName(final String name) { + this.expirationDateColumnName = name; + } + + + /** + * @param dataSource JDBC data source. + */ + public void setDataSource(final DataSource dataSource) { + this.dataSource = dataSource; + } + + + /** + * @param platform Database platform that indicates when special syntax + * is needed for database operations. + */ + public void setPlatform(final DatabasePlatform platform) { + this.platform = platform; + } + + + /** {@inheritDoc} */ + public void afterPropertiesSet() { + this.jdbcTemplate = new JdbcTemplate(this.dataSource); + this.jdbcTemplate.afterPropertiesSet(); + this.createSql = String.format( + "INSERT INTO %s (%s, %s, %s) VALUES(?, ?, ?)", + this.tableName, + this.applicationIdColumnName, + this.uniqueIdColumnName, + this.expirationDateColumnName); + this.updateAcquireSql = String.format( + "UPDATE %s SET %s=?, %s=? WHERE %s=?", + this.tableName, + this.uniqueIdColumnName, + this.expirationDateColumnName, + this.applicationIdColumnName); + this.updateReleaseSql = String.format( + "UPDATE %s SET %s=NULL, %s=NULL WHERE %s=? AND %s=?", + this.tableName, + this.uniqueIdColumnName, + this.expirationDateColumnName, + this.applicationIdColumnName, + this.uniqueIdColumnName); + + // Support platform-specific syntax for select query + final StringBuilder sb = new StringBuilder(); + sb.append(String.format("SELECT %s, %s FROM %s WHERE %s=?", + this.uniqueIdColumnName, + this.expirationDateColumnName, + this.tableName, + this.applicationIdColumnName)); + switch (this.platform) { + case HSQL: + case SqlServer: + // Neither HSQL nor SQL Server support FOR UPDATE + break; + default: + // SQL-92 compliant platforms support FOR UPDATE updatability clause + sb.append(" FOR UPDATE"); + break; + } + this.selectSql = sb.toString(); + } + + + /** + * @see org.jasig.cas.ticket.registry.support.LockingStrategy#acquire() + */ + @Transactional + public boolean acquire() { + boolean lockAcquired = false; + if (this.platform == DatabasePlatform.SqlServer) { + this.jdbcTemplate.execute("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"); + } + try { + final SqlRowSet rowSet = (SqlRowSet) this.jdbcTemplate.query( + this.selectSql, + new Object[] {this.applicationId}, + new SqlRowSetResultSetExtractor()); + final Timestamp expDate = getExpirationDate(); + if (!rowSet.next()) { + // No row exists for this applicationId so create it. + // Row is created with uniqueId of this instance + // which indicates the lock is initially held by this instance. + this.jdbcTemplate.update(this.createSql, new Object[] {this.applicationId, this.uniqueId, expDate}); + return true; + } + lockAcquired = canAcquire(rowSet); + if (lockAcquired) { + // Update unique ID of row to indicate this instance holds lock + this.jdbcTemplate.update(this.updateAcquireSql, new Object[] {this.uniqueId, expDate, this.applicationId}); + } + } finally { + // Always attempt to revert current connection to default isolation + // level on SQL Server + if (this.platform == DatabasePlatform.SqlServer) { + this.jdbcTemplate.execute("SET TRANSACTION ISOLATION LEVEL READ COMMITTED"); + } + } + return lockAcquired; + } + + + /** + * @see org.jasig.cas.ticket.registry.support.LockingStrategy#release() + */ + @Transactional + public void release() { + // Update unique ID of row to indicate this instance holds lock + this.jdbcTemplate.update(this.updateReleaseSql, new Object[] {this.applicationId, this.uniqueId}); + } + + + /** + * Determines whether this instance can acquire the lock. + * + * @param lockRow Row of lock data for this application ID. + * + * @return True if lock can be acquired, false otherwise. + */ + private boolean canAcquire(final SqlRowSet lockRow) { + if (lockRow.getString(this.uniqueIdColumnName) != null) { + final Calendar expCal = Calendar.getInstance(); + expCal.setTime(lockRow.getTimestamp(this.expirationDateColumnName)); + return Calendar.getInstance().after(expCal); + } + return true; + } + + + /** + * @return The expiration date for a lock acquired at the current system + * time + */ + private Timestamp getExpirationDate() { + final Calendar cal = Calendar.getInstance(); + cal.add(Calendar.SECOND, this.lockTimeout); + return new Timestamp(cal.getTimeInMillis()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategy.java new file mode 100644 index 0000000..67cb871 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategy.java @@ -0,0 +1,284 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry.support; + +import java.util.Calendar; +import java.util.Date; + +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.annotation.Transactional; + +/** + * JPA 2.0 implementation of an exclusive, non-reintrant lock. + * + * @author Marvin S. Addison + * @version $Revision: $ + * + */ +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} */ + @Transactional(readOnly = false) + public boolean acquire() { + Lock lock; + try { + lock = entityManager.find(Lock.class, applicationId, LockModeType.PESSIMISTIC_WRITE); + } catch (PersistenceException e) { + logger.debug("{} failed querying for {} lock.", new Object[] {uniqueId, applicationId, e}); + return false; + } + + boolean result = false; + if (lock != null) { + final Date expDate = 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 (expDate != null && new Date().after(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} */ + @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; + } + + + private boolean acquire(final EntityManager em, 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) { + lock = em.merge(lock); + } else { + lock.setApplicationId(applicationId); + em.persist(lock); + } + success = true; + } catch (PersistenceException e) { + success = false; + if (logger.isDebugEnabled()) { + logger.debug("{} could not obtain {} lock.", new Object[] {uniqueId, applicationId, e}); + } else { + logger.info("{} could not obtain {} lock.", uniqueId, applicationId); + } + } + return success; + } + + + /** + * Describes a database lock. + * + * @author Marvin S. Addison + * @version $Revision: $ + * + */ + @Entity + @Table(name = "locks") + public 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(String applicationId) { + this.applicationId = applicationId; + } + + /** + * @return the uniqueId + */ + public String getUniqueId() { + return uniqueId; + } + + /** + * @param uniqueId the uniqueId to set + */ + public void setUniqueId(String uniqueId) { + this.uniqueId = uniqueId; + } + + /** + * @return the expirationDate + */ + public Date getExpirationDate() { + return expirationDate; + } + + /** + * @param expirationDate the expirationDate to set + */ + public void setExpirationDate(Date expirationDate) { + this.expirationDate = expirationDate; + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/LockingStrategy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/LockingStrategy.java new file mode 100644 index 0000000..46622ad --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/LockingStrategy.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +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 + * @version $Revision$ + * @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/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 0000000..2963b87 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/NoOpLockingStrategy.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +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 + * @version $Revision$ + * @since 3.3.6 + * + */ +public class NoOpLockingStrategy implements LockingStrategy { + + /** + * @see org.jasig.cas.ticket.registry.support.LockingStrategy#acquire() + */ + 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 0000000..9f1f3c8 --- /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/HardTimeoutExpirationPolicy.java b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/HardTimeoutExpirationPolicy.java new file mode 100644 index 0000000..c292b14 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/HardTimeoutExpirationPolicy.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketState; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.1.2 + */ +public final class HardTimeoutExpirationPolicy implements ExpirationPolicy { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -1465997330804816888L; + + /** The time to kill in milliseconds. */ + private final long timeToKillInMilliSeconds; + + public HardTimeoutExpirationPolicy(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + 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 0000000..3645846 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicy.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class MultiTimeUseOrTimeoutExpirationPolicy implements + ExpirationPolicy { + + /** Serializable Unique ID. */ + private static final long serialVersionUID = 3257844372614558261L; + + /** The time to kill in millseconds. */ + private final long timeToKillInMilliSeconds; + + /** The maximum number of uses before expiration. */ + private final int numberOfUses; + + 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."); + + } + + public MultiTimeUseOrTimeoutExpirationPolicy(int numberOfUses, long timeToKill, TimeUnit timeUnit) { + this(numberOfUses, timeUnit.toMillis(timeToKill)); + } + + 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 0000000..16cb3b2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/NeverExpiresExpirationPolicy.java @@ -0,0 +1,41 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class NeverExpiresExpirationPolicy implements ExpirationPolicy { + + /** Serializable Unique ID. */ + private static final long serialVersionUID = 3833747698242303540L; + + 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 0000000..e548842 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicy.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.authentication.principal.RememberMeCredentials; +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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public final class RememberMeDelegatingExpirationPolicy implements ExpirationPolicy { + + /** Unique Id for Serialization */ + private static final long serialVersionUID = -575145836880428365L; + + @NotNull + private ExpirationPolicy rememberMeExpirationPolicy; + + @NotNull + private ExpirationPolicy sessionExpirationPolicy; + + public boolean isExpired(TicketState ticketState) { + final Boolean b = (Boolean) ticketState.getAuthentication().getAttributes().get(RememberMeCredentials.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 0000000..698b05c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicy.java @@ -0,0 +1,84 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of an expiration policy that adds the concept of saying that a + * ticket can only be used once every X millseconds to prevent misconfigured + * clients from consuming resources by doing constant redirects. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.5 + */ +public final class ThrottledUseAndTimeoutExpirationPolicy implements + ExpirationPolicy { + + private static final Logger log = LoggerFactory.getLogger(ThrottledUseAndTimeoutExpirationPolicy.class); + + /** Static ID for serialization. */ + private static final long serialVersionUID = -848036845536731268L; + + /** 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; + } + + public boolean isExpired(final TicketState ticketState) { + if (ticketState.getCountOfUses() == 0 + && (System.currentTimeMillis() - ticketState.getLastTimeUsed() < this.timeToKillInMilliSeconds)) { + if (log.isDebugEnabled()) { + log + .debug("Ticket is not expired due to a count of zero and the time being less than the timeToKillInMilliseconds"); + } + return false; + } + + if ((System.currentTimeMillis() - ticketState.getLastTimeUsed() >= this.timeToKillInMilliSeconds)) { + if (log.isDebugEnabled()) { + log + .debug("Ticket is expired due to the time being greater than the timeToKillInMilliseconds"); + } + return true; + } + + if ((System.currentTimeMillis() - ticketState.getLastTimeUsed() <= this.timeInBetweenUsesInMilliSeconds)) { + log + .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 0000000..2d28f8c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicy.java @@ -0,0 +1,97 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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. + * @version $Revision$ $Date$ + * @since 3.4.10 + */ +public final class TicketGrantingTicketExpirationPolicy implements ExpirationPolicy, InitializingBean { + + private static final Logger log = LoggerFactory.getLogger(TicketGrantingTicketExpirationPolicy.class); + + /** Static ID for serialization. */ + private static final long serialVersionUID = 2136490343650084287L; + + /** Maximum time this ticket is valid */ + private long maxTimeToLiveInMilliSeconds; + + /** Time to kill in milliseconds. */ + private long timeToKillInMilliSeconds; + + public void setMaxTimeToLiveInMilliSeconds(final long maxTimeToLiveInMilliSeconds){ + this.maxTimeToLiveInMilliSeconds = maxTimeToLiveInMilliSeconds; + } + + public void setTimeToKillInMilliSeconds(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + /** Convenient virtual property setter to set time in seconds */ + public void setMaxTimeToLiveInSeconds(final long maxTimeToLiveInSeconds){ + if(this.maxTimeToLiveInMilliSeconds == 0L) { + this.maxTimeToLiveInMilliSeconds = TimeUnit.SECONDS.toMillis(maxTimeToLiveInSeconds); + } + } + + /** Convenient virtual property setter to set time in seconds */ + public void setTimeToKillInSeconds(final long timeToKillInSeconds) { + if(this.timeToKillInMilliSeconds == 0L) { + this.timeToKillInMilliSeconds = TimeUnit.SECONDS.toMillis(timeToKillInSeconds); + } + } + + public void afterPropertiesSet() throws Exception { + Assert.isTrue((maxTimeToLiveInMilliSeconds >= timeToKillInMilliSeconds), "maxTimeToLiveInMilliSeconds must be greater than or equal to timeToKillInMilliSeconds."); + } + + public boolean isExpired(final TicketState ticketState) { + // Ticket has been used, check maxTimeToLive (hard window) + if ((System.currentTimeMillis() - ticketState.getCreationTime() >= maxTimeToLiveInMilliSeconds)) { + if (log.isDebugEnabled()) { + log.debug("Ticket is expired due to the time since creation being greater than the maxTimeToLiveInMilliSeconds"); + } + return true; + } + + // Ticket is within hard window, check timeToKill (sliding window) + if ((System.currentTimeMillis() - ticketState.getLastTimeUsed() >= timeToKillInMilliSeconds)) { + if (log.isDebugEnabled()) { + log.debug("Ticket is expired due to the time since last use being greater than the 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 0000000..3792d62 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicy.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketState; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class TimeoutExpirationPolicy implements ExpirationPolicy { + + /** Serializable ID. */ + private static final long serialVersionUID = 3545511790222979383L; + + /** The time to kill in milliseconds. */ + private final long timeToKillInMilliSeconds; + + public TimeoutExpirationPolicy(final long timeToKillInMilliSeconds) { + this.timeToKillInMilliSeconds = timeToKillInMilliSeconds; + } + + 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 0000000..09a79aa --- /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/AopUtils.java b/cas-server-core/src/main/java/org/jasig/cas/util/AopUtils.java new file mode 100644 index 0000000..9419413 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/AopUtils.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import org.aspectj.lang.JoinPoint; + + +/** + * Utility class to assist with AOP operations. + * + * @author Marvin S. Addison + * @version $Revision$ $Date$ + * @since 3.4 + * + */ +public final class 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 0000000..8a111b8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/ApplicationContextProvider.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package org.jasig.cas.util; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +public class ApplicationContextProvider implements ApplicationContextAware{ + private static ApplicationContext context = null; + + public static ApplicationContext getApplicationContext() { + return context; + } + public void setApplicationContext(ApplicationContext context) throws BeansException { + this.context = context; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/CalendarUtils.java b/cas-server-core/src/main/java/org/jasig/cas/util/CalendarUtils.java new file mode 100644 index 0000000..3acc2e5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/CalendarUtils.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import java.util.Calendar; +import java.util.Date; + + +public final class CalendarUtils { + + public static final String[] WEEKDAYS = new String[] {"UNDEFINED", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; + + private CalendarUtils() { + // nothing to do + } + + public static int getCurrentDayOfWeek() { + return getCurrentDayOfWeekFor(new Date()); + } + + public static int getCurrentDayOfWeekFor(final Date date) { + return getCalendarFor(date).get(Calendar.DAY_OF_WEEK); + } + + public static Calendar getCalendarFor(final Date date) { + final Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/CasHTTPSOAP11Encoder.java b/cas-server-core/src/main/java/org/jasig/cas/util/CasHTTPSOAP11Encoder.java new file mode 100644 index 0000000..38a39b8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/CasHTTPSOAP11Encoder.java @@ -0,0 +1,60 @@ +/* + * 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. + */ + +package org.jasig.cas.util; + +import org.opensaml.Configuration; +import org.opensaml.common.SAMLObject; +import org.opensaml.saml1.binding.encoding.HTTPSOAP11Encoder; +import org.opensaml.ws.soap.common.SOAPObjectBuilder; +import org.opensaml.ws.soap.soap11.Body; +import org.opensaml.ws.soap.soap11.Envelope; +import org.opensaml.ws.soap.util.SOAPConstants; +import org.opensaml.xml.XMLObjectBuilderFactory; + +/** + * 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 + */ +public final class CasHTTPSOAP11Encoder extends HTTPSOAP11Encoder { + private static final String OPENSAML_11_SOAP_NS_PREFIX = "SOAP-ENV"; + + @Override + protected Envelope buildSOAPMessage(final SAMLObject samlMessage) { + final XMLObjectBuilderFactory builderFactory = Configuration.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); + + body.getUnknownXMLObjects().add(samlMessage); + envelope.setBody(body); + + return envelope; + } +} 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 0000000..ca05989 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/CustomBeanValidationPostProcessor.java @@ -0,0 +1,54 @@ +/* + * 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. + */ + +package org.jasig.cas.util; + +import org.springframework.validation.beanvalidation.BeanValidationPostProcessor; + +import javax.validation.*; +import java.lang.annotation.ElementType; + + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.4 + * + */ +public final class CustomBeanValidationPostProcessor extends BeanValidationPostProcessor { + + public CustomBeanValidationPostProcessor() { + final Configuration configuration = Validation.byDefaultProvider().configure(); + configuration.traversableResolver(new TraversableResolver() { + public boolean isReachable(final Object o, final Path.Node node, final Class aClass, final Path path, final ElementType elementType) { + return true; + } + + public boolean isCascadable(final Object o, final Path.Node node, final Class aClass, final Path path, 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/DefaultLongNumericGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultLongNumericGenerator.java new file mode 100644 index 0000000..2e45218 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultLongNumericGenerator.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.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; + + public DefaultLongNumericGenerator() { + this(0); + // nothing to do + } + + public DefaultLongNumericGenerator(final long initialValue) { + this.count = new AtomicLong(initialValue); + } + + public long getNextLong() { + return this.getNextValue(); + } + + public String getNextNumberAsString() { + return Long.toString(this.getNextValue()); + } + + public int maxLength() { + return DefaultLongNumericGenerator.MAX_STRING_LENGTH; + } + + public int minLength() { + return DefaultLongNumericGenerator.MIN_STRING_LENGTH; + } + + 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 0000000..eb50321 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultRandomStringGenerator.java @@ -0,0 +1,87 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class DefaultRandomStringGenerator implements + RandomStringGenerator { + + /** The array of printable characters to be used in our random string. */ + private static final char[] PRINTABLE_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345679" + .toCharArray(); + + /** The default maximum length. */ + private static final int DEFAULT_MAX_RANDOM_LENGTH = 35; + + /** An instance of secure random to ensure randomness is secure. */ + private SecureRandom randomizer = new SecureRandom(); + + /** The maximum length the random string can be. */ + private final int maximumRandomLength; + + public DefaultRandomStringGenerator() { + this.maximumRandomLength = DEFAULT_MAX_RANDOM_LENGTH; + } + + public DefaultRandomStringGenerator(final int maxRandomLength) { + this.maximumRandomLength = maxRandomLength; + } + + public int getMinLength() { + return this.maximumRandomLength; + } + + public int getMaxLength() { + return this.maximumRandomLength; + } + + public String getNewString() { + final byte[] random = getNewStringAsBytes(); + + return convertBytesToString(random); + } + + + public byte[] getNewStringAsBytes() { + final byte[] random = new byte[this.maximumRandomLength]; + + this.randomizer.nextBytes(random); + + return random; + } + + 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 0000000..d46ce72 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/DefaultUniqueTicketIdGenerator.java @@ -0,0 +1,126 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class DefaultUniqueTicketIdGenerator implements + UniqueTicketIdGenerator { + + /** 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(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. + */ + public DefaultUniqueTicketIdGenerator(final int maxLength) { + this(maxLength, null); + } + + /** + * Creates an instance of DefaultUniqueTicketIdGenerator with default values + * including a {@link DefaultLongNumericGenerator} with a starting value of + * 1. + * + * @param suffix the value to append at the end of the unique id to ensure + * uniqueness across JVMs. + */ + public DefaultUniqueTicketIdGenerator(final String suffix) { + this.numericGenerator = new DefaultLongNumericGenerator(1); + this.randomStringGenerator = new DefaultRandomStringGenerator(); + + if (suffix != null) { + this.suffix = "-" + suffix; + } else { + this.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.numericGenerator = new DefaultLongNumericGenerator(1); + this.randomStringGenerator = new DefaultRandomStringGenerator(maxLength); + + if (suffix != null) { + this.suffix = "-" + suffix; + } else { + this.suffix = null; + } + } + + public String getNewTicketId(final String prefix) { + final String number = this.numericGenerator.getNextNumberAsString(); + final StringBuilder buffer = new StringBuilder(prefix.length() + 2 + + (this.suffix != null ? 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/HttpClient.java b/cas-server-core/src/main/java/org/jasig/cas/util/HttpClient.java new file mode 100644 index 0000000..51083b7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/HttpClient.java @@ -0,0 +1,274 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.util.Assert; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class HttpClient implements Serializable, DisposableBean { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = -5306738686476129516L; + + /** 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}; + + private static final Logger log = LoggerFactory.getLogger(HttpClient.class); + + private static ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(100); + + + /** List of HTTP status codes considered valid by this AuthenticationHandler. */ + @NotNull + @Size(min=1) + private int[] acceptableCodes = DEFAULT_ACCEPTABLE_CODES; + + @Min(0) + private int connectionTimeout = 5000; + + @Min(0) + private int readTimeout = 5000; + + private boolean followRedirects = true; + + + /** + * 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 + */ + public void setExecutorService(final ExecutorService executorService) { + Assert.notNull(executorService); + EXECUTOR_SERVICE = executorService; + } + + /** + * 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 url the url to send the message to + * @param message the message itself + * @param async true if you don't want to wait for the response, false otherwise. + * @return boolean if the message was sent, or async was used. false if the message failed. + */ + public boolean sendMessageToEndPoint(final String url, final String message, final boolean async) { + final Future result = EXECUTOR_SERVICE.submit(new MessageSender(url, message, this.readTimeout, this.connectionTimeout, this.followRedirects)); + + if (async) { + return true; + } + + try { + return result.get(); + } catch (final Exception e) { + return false; + } + } + + public boolean isValidEndPoint(final String url) { + try { + final URL u = new URL(url); + return isValidEndPoint(u); + } catch (final MalformedURLException e) { + log.error(e.getMessage(),e); + return false; + } + } + + public boolean isValidEndPoint(final URL url) { + HttpURLConnection connection = null; + InputStream is = null; + try { + connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(this.connectionTimeout); + connection.setReadTimeout(this.readTimeout); + connection.setInstanceFollowRedirects(this.followRedirects); + + connection.connect(); + + final int responseCode = connection.getResponseCode(); + + for (final int acceptableCode : this.acceptableCodes) { + if (responseCode == acceptableCode) { + if (log.isDebugEnabled()) { + log.debug("Response code from server matched " + responseCode + "."); + } + return true; + } + } + + if (log.isDebugEnabled()) { + log.debug("Response Code did not match any of the acceptable response codes. Code returned was " + responseCode); + } + + // if the response code is an error and we don't find that error acceptable above: + if (responseCode == 500) { + is = connection.getInputStream(); + final String value = IOUtils.toString(is); + log.error(String.format("There was an error contacting the endpoint: %s; The error was:\n%s", url.toExternalForm(), value)); + } + } catch (final IOException e) { + log.error(e.getMessage(),e); + } finally { + IOUtils.closeQuietly(is); + if (connection != null) { + connection.disconnect(); + } + } + return false; + } + + /** + * 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. + */ + public final void setAcceptableCodes(final int[] acceptableCodes) { + this.acceptableCodes = acceptableCodes; + } + + public void setConnectionTimeout(final int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public void setReadTimeout(final int readTimeout) { + this.readTimeout = readTimeout; + } + + /** + * Determines the behavior on receiving 3xx responses from HTTP endpoints. + * + * @param follow True to follow 3xx redirects (default), false otherwise. + */ + public void setFollowRedirects(final boolean follow) { + this.followRedirects = follow; + } + + public void destroy() throws Exception { + EXECUTOR_SERVICE.shutdown(); + } + + private static final class MessageSender implements Callable { + + private String url; + + private String message; + + private int readTimeout; + + private int connectionTimeout; + + private boolean followRedirects; + + public MessageSender(final String url, final String message, final int readTimeout, final int connectionTimeout, final boolean followRedirects) { + this.url = url; + this.message = message; + this.readTimeout = readTimeout; + this.connectionTimeout = connectionTimeout; + this.followRedirects = followRedirects; + } + + public Boolean call() throws Exception { + HttpURLConnection connection = null; + BufferedReader in = null; + try { + if (log.isDebugEnabled()) { + log.debug("Attempting to access " + url); + } + final URL logoutUrl = new URL(url); + final String output = "logoutRequest=" + URLEncoder.encode(message, "UTF-8"); + + connection = (HttpURLConnection) logoutUrl.openConnection(); + connection.setDoInput(true); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + connection.setReadTimeout(this.readTimeout); + connection.setConnectTimeout(this.connectionTimeout); + connection.setInstanceFollowRedirects(this.followRedirects); + connection.setRequestProperty("Content-Length", Integer.toString(output.getBytes().length)); + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + final DataOutputStream printout = new DataOutputStream(connection.getOutputStream()); + printout.writeBytes(output); + printout.flush(); + printout.close(); + + in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + + while (in.readLine() != null) { + // nothing to do + } + + if (log.isDebugEnabled()) { + log.debug("Finished sending message to" + url); + } + return true; + } catch (final SocketTimeoutException e) { + log.warn("Socket Timeout Detected while attempting to send message to [" + url + "]."); + return false; + } catch (final Exception e) { + log.warn("Error Sending message to url endpoint [" + url + "]. Error is [" + e.getMessage() + "]"); + return false; + } finally { + if (in != null) { + try { + in.close(); + } catch (final IOException e) { + // can't do anything + } + } + if (connection != null) { + connection.disconnect(); + } + } + } + + } +} 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 0000000..1ab4d39 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/LongNumericGenerator.java @@ -0,0 +1,36 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +/** + * Interface to guaranteed to return a long. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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/NumericGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/NumericGenerator.java new file mode 100644 index 0000000..1f460b7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/NumericGenerator.java @@ -0,0 +1,50 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +/** + * Interface to return a new sequential number for each call. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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/PrivateKeyFactoryBean.java b/cas-server-core/src/main/java/org/jasig/cas/util/PrivateKeyFactoryBean.java new file mode 100644 index 0000000..6a772ff --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/PrivateKeyFactoryBean.java @@ -0,0 +1,72 @@ +/* + * 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. + */ +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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public final class PrivateKeyFactoryBean extends AbstractFactoryBean { + + @NotNull + private Resource location; + + @NotNull + private String algorithm; + + protected Object createInstance() throws Exception { + final InputStream privKey = this.location.getInputStream(); + try { + final byte[] bytes = new byte[privKey.available()]; + privKey.read(bytes); + privKey.close(); + final PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(bytes); + KeyFactory factory = KeyFactory.getInstance(this.algorithm); + return factory.generatePrivate(privSpec); + } finally { + privKey.close(); + } + } + + 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 0000000..6c369c7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/PublicKeyFactoryBean.java @@ -0,0 +1,71 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import java.io.InputStream; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; + +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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public class PublicKeyFactoryBean extends AbstractFactoryBean { + + @NotNull + private Resource resource; + + @NotNull + private String algorithm; + + protected final Object createInstance() throws Exception { + final InputStream pubKey = this.resource.getInputStream(); + try { + 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); + } finally { + pubKey.close(); + } + } + + public Class getObjectType() { + return PublicKey.class; + } + + + public void setLocation(final Resource resource) { + this.resource = resource; + } + + public void setAlgorithm(final String algorithm) { + this.algorithm = algorithm; + } +} 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 0000000..8314384 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/RandomStringGenerator.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +/** + * Interface to return a random String. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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(); + + byte[] getNewStringAsBytes(); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/SamlCompliantUniqueTicketIdGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/util/SamlCompliantUniqueTicketIdGenerator.java new file mode 100644 index 0000000..b3ffff3 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/SamlCompliantUniqueTicketIdGenerator.java @@ -0,0 +1,91 @@ +/* + * 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. + */ + +package org.jasig.cas.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + +import org.opensaml.saml1.binding.artifact.SAML1ArtifactType0001; +import org.opensaml.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 + * @version $Revision$ $Date$ + * @since 3.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; + + 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."); + } + } + + /** + * We ignore prefixes for SAML compliance. + */ + public String getNewTicketId(final String prefix) { + if (saml2compliant) { + return new SAML2ArtifactType0004(ENDPOINT_ID, newAssertionHandle(), sourceIdDigest).base64Encode(); + } else { + return new SAML1ArtifactType0001(this.sourceIdDigest, newAssertionHandle()).base64Encode(); + } + } + + public void setSaml2compliant(final boolean saml2compliant) { + this.saml2compliant = saml2compliant; + } + + private byte[] newAssertionHandle() { + final byte[] handle = new byte[ASSERTION_HANDLE_SIZE]; + this.random.nextBytes(handle); + return handle; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/util/SamlUtils.java b/cas-server-core/src/main/java/org/jasig/cas/util/SamlUtils.java new file mode 100644 index 0000000..0b59728 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/SamlUtils.java @@ -0,0 +1,220 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.PublicKey; +import java.security.interfaces.DSAPublicKey; +import java.security.interfaces.RSAPublicKey; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +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.parsers.DocumentBuilderFactory; + + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.DOMBuilder; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; +import org.w3c.dom.Node; + +/** + * Utilities adopted from the Google sample code. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public final class SamlUtils { + + private static final String JSR_105_PROVIDER = "org.jcp.xml.dsig.internal.dom.XMLDSigRI"; + + private static final String SAML_PROTOCOL_NS_URI_V20 = "urn:oasis:names:tc:SAML:2.0:protocol"; + + private SamlUtils() { + // nothing to do + } + + public static String getCurrentDateAndTime() { + return getFormattedDateAndTime(new Date()); + } + + public static String getFormattedDateAndTime(final Date date) { + final DateFormat dateFormat = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss'Z'"); + // Google Does not set this. + // dateFormat.setTimeZone(UTC_TIME_ZONE); + return dateFormat.format(date); + } + + public static String signSamlResponse(final String samlResponse, + final PrivateKey privateKey, final PublicKey publicKey) { + final Document doc = constructDocumentFromXmlString(samlResponse); + + if (doc != null) { + final Element signedElement = signSamlElement(doc.getRootElement(), + privateKey, publicKey); + doc.setRootElement((Element) signedElement.detach()); + return new XMLOutputter().outputString(doc); + } + throw new RuntimeException("Error signing SAML Response: Null document"); + } + + public static Document constructDocumentFromXmlString(String xmlString) { + try { + final SAXBuilder builder = new SAXBuilder(); + return builder + .build(new ByteArrayInputStream(xmlString.getBytes())); + } catch (final Exception e) { + return null; + } + } + + private static Element signSamlElement(Element element, PrivateKey privKey, + PublicKey pubKey) { + try { + final String providerName = System.getProperty("jsr105Provider", + JSR_105_PROVIDER); + 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 + 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) + 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 + DOMSignContext dsc = new DOMSignContext(privKey, w3cElement); + + org.w3c.dom.Node xmlSigInsertionPoint = getXmlSignatureInsertLocation(w3cElement); + dsc.setNextSibling(xmlSigInsertionPoint); + + // Marshal, generate (and sign) the enveloped signature + 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); + } + } + + private static Node getXmlSignatureInsertLocation(org.w3c.dom.Element elem) { + org.w3c.dom.Node insertLocation = null; + org.w3c.dom.NodeList nodeList = elem.getElementsByTagNameNS( + SAML_PROTOCOL_NS_URI_V20, "Extensions"); + if (nodeList.getLength() != 0) { + insertLocation = nodeList.item(nodeList.getLength() - 1); + } else { + nodeList = elem.getElementsByTagNameNS(SAML_PROTOCOL_NS_URI_V20, + "Status"); + insertLocation = nodeList.item(nodeList.getLength() - 1); + } + return insertLocation; + } + + private static org.w3c.dom.Element toDom(final Element element) { + return toDom(element.getDocument()).getDocumentElement(); + } + + private static 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(); + final DocumentBuilderFactory dbf = DocumentBuilderFactory + .newInstance(); + dbf.setNamespaceAware(true); + return dbf.newDocumentBuilder().parse( + new ByteArrayInputStream(xmlBytes)); + } catch (final Exception e) { + return null; + } + } + + private static Element toJdom(final org.w3c.dom.Element e) { + return new DOMBuilder().build(e); + } +} 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 0000000..702ab26 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/SpringAwareMessageMessageInterpolator.java @@ -0,0 +1,58 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.4 + */ +public final class SpringAwareMessageMessageInterpolator implements MessageInterpolator, MessageSourceAware { + + private MessageInterpolator defaultMessageInterpolator = Validation.byDefaultProvider().configure().getDefaultMessageInterpolator(); + + private MessageSource messageSource; + + public void setMessageSource(final MessageSource messageSource) { + this.messageSource = messageSource; + } + + public String interpolate(final String s, final Context context) { + return interpolate(s, context, LocaleContextHolder.getLocale()); + } + + 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 0000000..70a4e0c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/util/UniqueTicketIdGenerator.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +/** + * Interface that enables for pluggable unique ticket Ids strategies. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +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/package.html b/cas-server-core/src/main/java/org/jasig/cas/util/package.html new file mode 100644 index 0000000..7a9d801 --- /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/validation/AbstractCasProtocolValidationSpecification.java b/cas-server-core/src/main/java/org/jasig/cas/validation/AbstractCasProtocolValidationSpecification.java new file mode 100644 index 0000000..25b2204 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/AbstractCasProtocolValidationSpecification.java @@ -0,0 +1,75 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.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; + + public AbstractCasProtocolValidationSpecification() { + this.renew = DEFAULT_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; + } + + public final boolean isSatisfiedBy(final Assertion assertion) { + return isSatisfiedByInternal(assertion) + && ((!this.renew) || (assertion.isFromNewLogin() && this.renew)); + } + + /** + * Template method to allow for additional checks by subclassed methods + * without needing to call super.isSatisfiedBy(...). + */ + protected abstract boolean isSatisfiedByInternal(final Assertion assertion); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/Assertion.java b/cas-server-core/src/main/java/org/jasig/cas/validation/Assertion.java new file mode 100644 index 0000000..9c1ac4f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Assertion.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +import java.io.Serializable; +import java.util.List; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; + +/** + * Return from CentralAuthenticationService.validateServiceTicket(String, + * Service), the Assertion contains a chain of Principal objects. The first is + * the User's login Principal, while any others are Proxy Principals. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +public interface Assertion extends Serializable { + + /** + * Get a List of Authentications which represent the owners of the + * GrantingTickets which granted the ticket that was validated. The first + * Authentication of this list is the Authentication which originally + * authenticated to CAS to obtain the first Granting Ticket. Subsequent + * Authentication are those associated with GrantingTickets that were + * granted from that original granting ticket. The last Authentication in + * this List is that associated with the GrantingTicket that was the + * immediate grantor of the ticket that was validated. The List returned by + * this method will contain at least one Authentication. + * + * @return a List of Authentication + */ + 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/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 0000000..9ce1abb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecification.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class Cas10ProtocolValidationSpecification extends + AbstractCasProtocolValidationSpecification { + + public Cas10ProtocolValidationSpecification() { + super(); + } + + public Cas10ProtocolValidationSpecification(final boolean renew) { + super(renew); + } + + 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 0000000..0963c56 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecification.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class Cas20ProtocolValidationSpecification extends + AbstractCasProtocolValidationSpecification { + + public Cas20ProtocolValidationSpecification() { + super(); + } + + public Cas20ProtocolValidationSpecification(final boolean renew) { + super(renew); + } + + 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 0000000..1910cce --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecification.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class Cas20WithoutProxyingValidationSpecification extends + AbstractCasProtocolValidationSpecification { + + public Cas20WithoutProxyingValidationSpecification() { + super(); + } + + public Cas20WithoutProxyingValidationSpecification(final boolean renew) { + super(renew); + } + + protected boolean isSatisfiedByInternal(final Assertion assertion) { + return (assertion.getChainedAuthentications().size() == 1); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/ImmutableAssertionImpl.java b/cas-server-core/src/main/java/org/jasig/cas/validation/ImmutableAssertionImpl.java new file mode 100644 index 0000000..3ed4809 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/ImmutableAssertionImpl.java @@ -0,0 +1,99 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +import java.util.Collections; +import java.util.List; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.springframework.util.Assert; + +/** + * Default implementation of the Assertion interface which returns the minimum + * number of attributes required to conform to the CAS 2 protocol. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class ImmutableAssertionImpl implements Assertion { + + /** Unique Id for Serialization. */ + private static final long serialVersionUID = -1921502350732798866L; + + /** The list of principals. */ + private final List principals; + + /** Was this the result of a new login. */ + private final boolean fromNewLogin; + + /** The service we are asserting this ticket for. */ + private final Service service; + + /** + * Constructs a new ImmutableAssertion out of the given parameters. + * + * @param principals the chain of principals + * @param service The service we are asserting this ticket for. + * @param fromNewLogin was the service ticket from a new login. + * @throws IllegalArgumentException if there are no principals. + */ + public ImmutableAssertionImpl(final List principals, final Service service, + final boolean fromNewLogin) { + Assert.notNull(principals, "principals cannot be null"); + Assert.notNull(service, "service cannot be null"); + Assert.notEmpty(principals, "principals cannot be empty"); + + this.principals = principals; + this.service = service; + this.fromNewLogin = fromNewLogin; + } + + public List getChainedAuthentications() { + return Collections.unmodifiableList(this.principals); + } + + public boolean isFromNewLogin() { + return this.fromNewLogin; + } + + public Service getService() { + return this.service; + } + + public boolean equals(final Object o) { + if (o == null + || !this.getClass().isAssignableFrom(o.getClass())) { + return false; + } + + final Assertion a = (Assertion) o; + + return this.service.equals(a.getService()) && this.fromNewLogin == a.isFromNewLogin() && this.principals.equals(a.getChainedAuthentications()); + } + + public int hashCode() { + return 15 * this.service.hashCode() ^ this.principals.hashCode(); + } + + public String toString() { + return "[principals={" + this.principals.toString() + "} for service=" + this.service.toString() + "]"; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/validation/ValidationSpecification.java b/cas-server-core/src/main/java/org/jasig/cas/validation/ValidationSpecification.java new file mode 100644 index 0000000..dba20b8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/validation/ValidationSpecification.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +/** + * An interface to impose restrictions and requirements on validations (e.g. + * renew=true). + * + * @author William G. Thompson, Jr. + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ */ +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/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 0000000..52027c3 --- /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 0000000..47762c7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/DelegateController.java @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package org.jasig.cas.web; + +import org.springframework.web.servlet.mvc.AbstractController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Abtsract 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 + * @version $Id$ + * @since 3.5 + */ +public abstract class DelegateController extends AbstractController{ + /** + * 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/DelegatingController.java b/cas-server-core/src/main/java/org/jasig/cas/web/DelegatingController.java new file mode 100644 index 0000000..a02a06c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/DelegatingController.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package org.jasig.cas.web; + +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; +import org.springframework.web.servlet.mvc.Controller; + +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 + * @version $Id$ + * @since 3.5 + */ +public class DelegatingController extends AbstractController { + List delegates; + /** View if Service Ticket Validation Fails. */ + private static final String DEFAULT_ERROR_VIEW_NAME = "casServiceFailureView"; + + /** 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 + */ + protected final ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception { + for (DelegateController delegate : delegates) { + if (delegate.canHandle(request, response)) { + return delegate.handleRequest(request, response); + } + } + return generateErrorView("INVALID_REQUEST", "INVALID_REQUEST", null); + } + + 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(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-core/src/main/java/org/jasig/cas/web/HealthCheckController.java b/cas-server-core/src/main/java/org/jasig/cas/web/HealthCheckController.java new file mode 100644 index 0000000..0faecda --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/HealthCheckController.java @@ -0,0 +1,86 @@ +/* + * 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. + */ +package org.jasig.cas.web; + +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.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +/** + * Reports overall CAS health based on the observations of the configured {@link HealthCheckMonitor} instance. + * + * @author Marvin S. Addison + * @version $Revision: $ + */ +public class HealthCheckController extends AbstractController { + + /** Prefix for custom response headers with health check details. */ + private static final String HEADER_PREFIX = "X-CAS-"; + + @NotNull + private HealthCheckMonitor healthCheckMonitor; + + + /** + * Sets the health check monitor used to observe system health. + * @param monitor Health monitor configured with subordinate monitors that observe specific aspects of overall + * system health. + */ + public void setHealthCheckMonitor(final HealthCheckMonitor monitor) { + this.healthCheckMonitor = monitor; + } + + + /** {@inheritDoc} */ + 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-core/src/main/java/org/jasig/cas/web/LogoutController.java b/cas-server-core/src/main/java/org/jasig/cas/web/LogoutController.java new file mode 100644 index 0000000..61177f7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/LogoutController.java @@ -0,0 +1,129 @@ +/* + * 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. + */ +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.SimpleWebApplicationServiceImpl; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; +import org.springframework.web.servlet.view.RedirectView; + +/** + * Controller to delete ticket granting ticket cookie in order to log out of + * single sign on. This controller implements the idea of the ESUP Portail's + * Logout patch to allow for redirecting to a url on logout. It also exposes a + * log out link to the view via the WebConstants.LOGOUT constant. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class LogoutController extends AbstractController { + + /** The CORE to which we delegate for all CAS functionality. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + /** CookieGenerator for TGT Cookie */ + @NotNull + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + /** CookieGenerator for Warn Cookie */ + @NotNull + private CookieRetrievingCookieGenerator warnCookieGenerator; + + /** Logout view name. */ + @NotNull + private String logoutView; + + @NotNull + private ServicesManager servicesManager; + + /** + * Boolean to determine if we will redirect to any url provided in the + * service request parameter. + */ + private boolean followServiceRedirects; + + public LogoutController() { + setCacheSeconds(0); + } + + protected ModelAndView handleRequestInternal( + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request); + final String service = request.getParameter("service"); + + if (ticketGrantingTicketId != null) { + this.centralAuthenticationService + .destroyTicketGrantingTicket(ticketGrantingTicketId); + + this.ticketGrantingTicketCookieGenerator.removeCookie(response); + this.warnCookieGenerator.removeCookie(response); + } + + if (this.followServiceRedirects && service != null) { + final RegisteredService rService = this.servicesManager.findServiceBy(new SimpleWebApplicationServiceImpl(service)); + + if (rService != null && rService.isEnabled()) { + return new ModelAndView(new RedirectView(service)); + } + } + + return new ModelAndView(this.logoutView); + } + + public void setTicketGrantingTicketCookieGenerator( + final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator) { + this.ticketGrantingTicketCookieGenerator = ticketGrantingTicketCookieGenerator; + } + + public void setWarnCookieGenerator(final CookieRetrievingCookieGenerator warnCookieGenerator) { + this.warnCookieGenerator = warnCookieGenerator; + } + + /** + * @param centralAuthenticationService The centralAuthenticationService to + * set. + */ + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + public void setFollowServiceRedirects(final boolean followServiceRedirects) { + this.followServiceRedirects = followServiceRedirects; + } + + public void setLogoutView(final String logoutView) { + this.logoutView = logoutView; + } + + public void setServicesManager(final ServicesManager servicesManager) { + this.servicesManager = servicesManager; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/OpenIdProviderController.java b/cas-server-core/src/main/java/org/jasig/cas/web/OpenIdProviderController.java new file mode 100644 index 0000000..7ad9a5f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/OpenIdProviderController.java @@ -0,0 +1,49 @@ +/* + * 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. + */ +package org.jasig.cas.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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public final class OpenIdProviderController extends AbstractController { + + @NotNull + private String loginUrl; + + 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-core/src/main/java/org/jasig/cas/web/ProxyController.java b/cas-server-core/src/main/java/org/jasig/cas/web/ProxyController.java new file mode 100644 index 0000000..056ce77 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/ProxyController.java @@ -0,0 +1,119 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class ProxyController extends AbstractController { + + /** View for if the creation of a "Proxy" Ticket Fails. */ + private static final String CONST_PROXY_FAILURE = "casProxyFailureView"; + + /** View for if the creation of a "Proxy" Ticket Succeeds. */ + private static final String CONST_PROXY_SUCCESS = "casProxySuccessView"; + + /** 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; + + public ProxyController() { + setCacheSeconds(0); + } + + /** + * @return ModelAndView containing a view name of either + * casProxyFailureView or casProxySuccessView + */ + 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 (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}); + } + } + + private Service getTargetService(final HttpServletRequest request) { + return SimpleWebApplicationServiceImpl.createServiceFrom(request); + } + + 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-core/src/main/java/org/jasig/cas/web/ServiceValidateController.java b/cas-server-core/src/main/java/org/jasig/cas/web/ServiceValidateController.java new file mode 100644 index 0000000..08efdb5 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/ServiceValidateController.java @@ -0,0 +1,255 @@ +/* + * 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. + */ +package org.jasig.cas.web; + +import java.net.URL; +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.Credentials; +import org.jasig.cas.authentication.principal.HttpBasedServiceCredentials; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.TicketValidationException; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ValidationSpecification; +import org.jasig.cas.validation.Cas20ProtocolValidationSpecification; +import org.jasig.cas.web.support.ArgumentExtractor; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.ServletRequestDataBinder; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.AbstractController; + +/** + * Process the /validate and /serviceValidate 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class ServiceValidateController extends DelegateController { + + /** View if Service Ticket Validation Fails. */ + private static final String DEFAULT_SERVICE_FAILURE_VIEW_NAME = "casServiceFailureView"; + + /** View if Service Ticket Validation Succeeds. */ + private static final String DEFAULT_SERVICE_SUCCESS_VIEW_NAME = "casServiceSuccessView"; + + /** Constant representing the PGTIOU in the model. */ + private static final String MODEL_PROXY_GRANTING_TICKET_IOU = "pgtIou"; + + /** Constant representing the Assertion in the model. */ + private static final String MODEL_ASSERTION = "assertion"; + + /** 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 request the HttpServletRequest object. + * @return the credentials or null if there was an error or no credentials + * provided. + */ + protected Credentials getServiceCredentialsFromRequest(final HttpServletRequest request) { + final String pgtUrl = request.getParameter("pgtUrl"); + if (StringUtils.hasText(pgtUrl)) { + try { + return new HttpBasedServiceCredentials(new URL(pgtUrl)); + } catch (final Exception e) { + logger.error("Error constructing pgtUrl", e); + } + } + + return null; + } + + protected void initBinder(final HttpServletRequest request, final ServletRequestDataBinder binder) { + binder.setRequiredFields("renew"); + } + + 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) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Could not process request; Service: %s, Service Ticket Id: %s", service, serviceTicketId)); + } + return generateErrorView("INVALID_REQUEST", "INVALID_REQUEST", null); + } + + try { + final Credentials serviceCredentials = getServiceCredentialsFromRequest(request); + String proxyGrantingTicketId = null; + + // XXX should be able to validate AND THEN use + if (serviceCredentials != null) { + try { + proxyGrantingTicketId = this.centralAuthenticationService + .delegateTicketGrantingTicket(serviceTicketId, + serviceCredentials); + } catch (final TicketException e) { + logger.error("TicketException generating ticket for: " + + serviceCredentials, e); + } + } + + 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)) { + if (logger.isDebugEnabled()) { + logger.debug("ServiceTicket [" + serviceTicketId + "] does not satisfy validation specification."); + } + return generateErrorView("INVALID_TICKET", "INVALID_TICKET_SPEC", null); + } + + onSuccessfulValidation(serviceTicketId, assertion); + + final ModelAndView success = new ModelAndView(this.successView); + success.addObject(MODEL_ASSERTION, assertion); + + if (serviceCredentials != null && proxyGrantingTicketId != null) { + final String proxyIou = this.proxyHandler.handle(serviceCredentials, proxyGrantingTicketId); + success.addObject(MODEL_PROXY_GRANTING_TICKET_IOU, proxyIou); + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Successfully validated service ticket: %s", serviceTicketId)); + } + + return success; + } catch (final TicketValidationException e) { + return generateErrorView(e.getCode(), e.getCode(), new Object[] {serviceTicketId, e.getOriginalService().getId(), service.getId()}); + } catch (final TicketException te) { + return generateErrorView(te.getCode(), te.getCode(), + new Object[] {serviceTicketId}); + } catch (final UnauthorizedServiceException e) { + return generateErrorView(e.getMessage(), e.getMessage(), null); + } + } + + protected void onSuccessfulValidation(final String serviceTicketId, final Assertion assertion) { + // template method with nothing to do. + } + + 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; + } + + private ValidationSpecification getCommandClass() { + try { + return (ValidationSpecification) this.validationSpecificationClass.newInstance(); + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean canHandle(HttpServletRequest request, 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; + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/StatisticsController.java b/cas-server-core/src/main/java/org/jasig/cas/web/StatisticsController.java new file mode 100644 index 0000000..97b9e8f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/StatisticsController.java @@ -0,0 +1,129 @@ +/* + * 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. + */ +package org.jasig.cas.web; + +import org.jasig.cas.ticket.ServiceTicket; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.perf4j.log4j.GraphingStatisticsAppender; +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.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.3.5 + */ +public final class StatisticsController extends AbstractController { + + 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 final TicketRegistry ticketRegistry; + + private final Date upTimeStartDate = new Date(); + + private String casTicketSuffix; + + public StatisticsController(final TicketRegistry ticketRegistry) { + this.ticketRegistry = ticketRegistry; + } + + public void setCasTicketSuffix(final String casTicketSuffix) { + this.casTicketSuffix = casTicketSuffix; + } + + @Override + protected ModelAndView handleRequestInternal(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) throws Exception { + final ModelAndView modelAndView = new ModelAndView("viewStatisticsView"); + 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", Runtime.getRuntime().totalMemory() / 1024 / 1024); + modelAndView.addObject("maxMemory", Runtime.getRuntime().maxMemory() / 1024 / 1024); + modelAndView.addObject("freeMemory", Runtime.getRuntime().freeMemory() / 1024 / 1024); + 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.ticketRegistry.getTickets(); + + 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) { + // this means the ticket registry doesn't support this information. + } + + final Collection appenders = GraphingStatisticsAppender.getAllGraphingStatisticsAppenders(); + + modelAndView.addObject("unexpiredTgts", unexpiredTgts); + modelAndView.addObject("unexpiredSts", unexpiredSts); + modelAndView.addObject("expiredTgts", expiredTgts); + modelAndView.addObject("expiredSts", expiredSts); + modelAndView.addObject("pageTitle", modelAndView.getViewName()); + modelAndView.addObject("graphingStatisticAppenders", appenders); + + return modelAndView; + } + + 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(new Double(time).intValue()) + " "+ label + " " + calculateUptime(newDifference, calculations, labels); + + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/bind/CredentialsBinder.java b/cas-server-core/src/main/java/org/jasig/cas/web/bind/CredentialsBinder.java new file mode 100644 index 0000000..3e116f7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/bind/CredentialsBinder.java @@ -0,0 +1,63 @@ +/* + * 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. + */ +package org.jasig.cas.web.bind; + +import javax.servlet.http.HttpServletRequest; +import org.jasig.cas.authentication.principal.Credentials; + +/** + * Interface for a class that can bind items stored in the request to a + * particular credentials implementation. This allows for binding beyond the + * basic JavaBean/Request parameter binding that is handled by Spring + * automatically. Implementations are free to pass part or all of the + * HttpServletRequest to the Credentials. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + *

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

+ * + * @deprecated Future versions of CAS will provide a mechanism to gain access to standard items from the Request object. + */ +@Deprecated +public interface CredentialsBinder { + + /** + * Method to allow manually binding attributes from the request object to + * properties of the credentials. Useful when there is no mapping of + * attribute to property for the usual Spring binding to handle. + * + * @param request The HttpServletRequest from which we wish to bind + * credentials to + * @param credentials The credentials we will be doing custom binding to. + */ + void bind(HttpServletRequest request, Credentials credentials); + + /** + * Method to determine if a CredentialsBinder supports a specific class or + * not. + * + * @param clazz The class to determine is supported or not + * @return true if this class is supported by the CredentialsBinder, false + * otherwise. + */ + boolean supports(Class clazz); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/bind/package.html b/cas-server-core/src/main/java/org/jasig/cas/web/bind/package.html new file mode 100644 index 0000000..3d8823e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/bind/package.html @@ -0,0 +1,32 @@ + + + +

The bind package is related to the binding of attributes and +properties from the HttpServletRequest object to the Credentials class. +

+

In the Spring Framework, binding is a term used to describe mapping +request parameters to bean properties on a command object. In CAS terms, +binding is the additional binding that occurs beyond the Spring binding. +This type of binding is contained in a CredentialsBinder class that is +plugged into various components in the web tier.

+ + 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 0000000..44c7036 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AbstractNonInteractiveCredentialsAction.java @@ -0,0 +1,145 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +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; + +/** + * Abstract class to handle the retrieval and authentication of non-interactive + * credentials such as client certificates, NTLM, etc. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.4 + */ +public abstract class AbstractNonInteractiveCredentialsAction extends + AbstractAction { + + /** Instance of CentralAuthenticationService. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + protected final boolean isRenewPresent(final RequestContext context) { + return StringUtils.hasText(context.getRequestParameters().get("renew")); + } + + protected final Event doExecute(final RequestContext context) { + final Credentials credentials = constructCredentialsFromRequest(context); + + if (credentials == null) { + return error(); + } + + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + final Service service = WebUtils.getService(context); + + if (isRenewPresent(context) + && ticketGrantingTicketId != null + && service != null) { + + try { + final String serviceTicketId = this.centralAuthenticationService + .grantServiceTicket(ticketGrantingTicketId, + service, + credentials); + WebUtils.putServiceTicketInRequestScope(context, + serviceTicketId); + return result("warn"); + } catch (final TicketException e) { + if (e.getCause() != null + && AuthenticationException.class.isAssignableFrom(e + .getCause().getClass())) { + onError(context, credentials); + return error(); + } + this.centralAuthenticationService + .destroyTicketGrantingTicket(ticketGrantingTicketId); + if (logger.isDebugEnabled()) { + logger + .debug( + "Attempted to generate a ServiceTicket using renew=true with different credentials", + e); + } + } + } + + try { + WebUtils.putTicketGrantingTicketInRequestScope( + context, + this.centralAuthenticationService + .createTicketGrantingTicket(credentials)); + onSuccess(context, credentials); + return success(); + } catch (final TicketException e) { + onError(context, credentials); + return error(); + } + } + + public final void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Hook method to allow for additional processing of the response before + * returning an error event. + * + * @param context the context for this specific request. + * @param credentials the credentials for this request. + */ + protected void onError(final RequestContext context, + final Credentials credentials) { + // 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 credentials the credentials for this request. + */ + protected void onSuccess(final RequestContext context, + final Credentials credentials) { + // default implementation does nothing + } + + /** + * Abstract method to implement to construct the credentials from the + * request object. + * + * @param context the context for this request. + * @return the constructed credentials or null if none could be constructed + * from the request. + */ + protected abstract Credentials constructCredentialsFromRequest( + final RequestContext context); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/AuthenticationViaFormAction.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AuthenticationViaFormAction.java new file mode 100644 index 0000000..1beeb29 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/AuthenticationViaFormAction.java @@ -0,0 +1,183 @@ +/* + * 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. + */ +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.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.web.bind.CredentialsBinder; +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.util.StringUtils; +import org.springframework.web.util.CookieGenerator; +import org.springframework.webflow.execution.RequestContext; + +/** + * Action to authenticate credentials and retrieve a TicketGrantingTicket for + * those credentials. If there is a request for renew, then it also generates + * the Service Ticket required. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.4 + */ +public class AuthenticationViaFormAction { + + /** + * Binder that allows additional binding of form object beyond Spring + * defaults. + */ + private CredentialsBinder credentialsBinder; + + /** Core we delegate to for handling all ticket related tasks. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + @NotNull + private CookieGenerator warnCookieGenerator; + + protected Logger logger = LoggerFactory.getLogger(getClass()); + + public final void doBind(final RequestContext context, final Credentials credentials) throws Exception { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + + if (this.credentialsBinder != null && this.credentialsBinder.supports(credentials.getClass())) { + this.credentialsBinder.bind(request, credentials); + } + } + + public final String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext) throws Exception { + // Validate login ticket + final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context); + final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context); + if (!authoritativeLoginTicket.equals(providedLoginTicket)) { + this.logger.warn("Invalid login ticket " + providedLoginTicket); + final String code = "INVALID_TICKET"; + messageContext.addMessage( + new MessageBuilder().error().code(code).arg(providedLoginTicket).defaultText(code).build()); + return "error"; + } + + final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context); + final Service service = WebUtils.getService(context); + if (StringUtils.hasText(context.getRequestParameters().get("renew")) && ticketGrantingTicketId != null && service != null) { + + try { + final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicketId, service, credentials); + WebUtils.putServiceTicketInRequestScope(context, serviceTicketId); + putWarnCookieIfRequestParameterPresent(context); + return "warn"; + } catch (final TicketException e) { + if (isCauseAuthenticationException(e)) { + populateErrorsInstance(e, messageContext); + return getAuthenticationExceptionEventId(e); + } + + this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId); + if (logger.isDebugEnabled()) { + logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials", e); + } + } + } + + try { + WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials)); + putWarnCookieIfRequestParameterPresent(context); + return "success"; + } catch (final TicketException e) { + populateErrorsInstance(e, messageContext); + if (isCauseAuthenticationException(e)) + return getAuthenticationExceptionEventId(e); + return "error"; + } + } + + + private void populateErrorsInstance(final TicketException e, final MessageContext messageContext) { + + try { + messageContext.addMessage(new MessageBuilder().error().code(e.getCode()).defaultText(e.getCode()).build()); + } catch (final Exception fe) { + logger.error(fe.getMessage(), fe); + } + } + + private void putWarnCookieIfRequestParameterPresent(final RequestContext context) { + final HttpServletResponse response = WebUtils.getHttpServletResponse(context); + + if (StringUtils.hasText(context.getExternalContext().getRequestParameterMap().get("warn"))) { + this.warnCookieGenerator.addCookie(response, "true"); + } else { + this.warnCookieGenerator.removeCookie(response); + } + } + + private AuthenticationException getAuthenticationExceptionAsCause(final TicketException e) { + return (AuthenticationException) e.getCause(); + } + + private String getAuthenticationExceptionEventId(final TicketException e) { + final AuthenticationException authEx = getAuthenticationExceptionAsCause(e); + + if (this.logger.isDebugEnabled()) + this.logger.debug("An authentication error has occurred. Returning the event id " + authEx.getType()); + + return authEx.getType(); + } + + private boolean isCauseAuthenticationException(final TicketException e) { + return e.getCause() != null && AuthenticationException.class.isAssignableFrom(e.getCause().getClass()); + } + + public final void setCentralAuthenticationService(final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + /** + * Set a CredentialsBinder for additional binding of the HttpServletRequest + * to the Credentials instance, beyond our default binding of the + * Credentials as a Form Object in Spring WebMVC parlance. By the time we + * invoke this CredentialsBinder, we have already engaged in default binding + * such that for each HttpServletRequest parameter, if there was a JavaBean + * property of the Credentials implementation of the same name, we have set + * that property to be the value of the corresponding request parameter. + * This CredentialsBinder plugin point exists to allow consideration of + * things other than HttpServletRequest parameters in populating the + * Credentials (or more sophisticated consideration of the + * HttpServletRequest parameters). + * + * @param credentialsBinder the credentials binder to set. + */ + public final void setCredentialsBinder(final CredentialsBinder credentialsBinder) { + this.credentialsBinder = credentialsBinder; + } + + public final void setWarnCookieGenerator(final CookieGenerator warnCookieGenerator) { + this.warnCookieGenerator = warnCookieGenerator; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/CasDefaultFlowUrlHandler.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/CasDefaultFlowUrlHandler.java new file mode 100644 index 0000000..57b50ca --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/CasDefaultFlowUrlHandler.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +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; + +/** + * Provides special handling for parameters in requests made to the CAS login + * webflow. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.4 + */ +public class CasDefaultFlowUrlHandler extends DefaultFlowUrlHandler { + + @Override + public String createFlowExecutionUrl(final String flowId, final String flowExecutionKey, final HttpServletRequest request) { + final StringBuffer builder = new StringBuffer(); + builder.append(request.getRequestURI()); + builder.append("?"); + appendQueryParameters(builder, request.getParameterMap(), 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-core/src/main/java/org/jasig/cas/web/flow/GenerateLoginTicketAction.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/GenerateLoginTicketAction.java new file mode 100644 index 0000000..11de163 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/GenerateLoginTicketAction.java @@ -0,0 +1,60 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import javax.validation.constraints.NotNull; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jasig.cas.util.UniqueTicketIdGenerator; +import org.jasig.cas.web.support.WebUtils; +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 + * @version $Revision$ $Date$ + * @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 Log logger = LogFactory.getLog(getClass()); + + + @NotNull + private UniqueTicketIdGenerator ticketIdGenerator; + + public final String generate(final RequestContext context) { + final String loginTicket = this.ticketIdGenerator.getNewTicketId(PREFIX); + this.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-core/src/main/java/org/jasig/cas/web/flow/GenerateServiceTicketAction.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/GenerateServiceTicketAction.java new file mode 100644 index 0000000..d5240e8 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/GenerateServiceTicketAction.java @@ -0,0 +1,75 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.ticket.TicketException; +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 + * @version $Revision$ $Date$ + * @since 3.0.4 + */ +public final class GenerateServiceTicketAction extends AbstractAction { + + /** Instance of CentralAuthenticationService. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + protected Event doExecute(final RequestContext context) { + final Service service = WebUtils.getService(context); + final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context); + + try { + final String serviceTicketId = this.centralAuthenticationService + .grantServiceTicket(ticketGrantingTicket, + service); + WebUtils.putServiceTicketInRequestScope(context, + serviceTicketId); + return success(); + } catch (final TicketException e) { + if (isGatewayPresent(context)) { + return result("gateway"); + } + } + + return error(); + } + + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } + + protected boolean isGatewayPresent(final RequestContext context) { + return StringUtils.hasText(context.getExternalContext() + .getRequestParameterMap().get("gateway")); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/InitialFlowSetupAction.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/InitialFlowSetupAction.java new file mode 100644 index 0000000..1e43c90 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/InitialFlowSetupAction.java @@ -0,0 +1,110 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.web.support.ArgumentExtractor; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +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; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class InitialFlowSetupAction extends AbstractAction { + + /** 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 = false; + + 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; + } + + context.getFlowScope().put( + "ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request)); + context.getFlowScope().put( + "warnCookieValue", + Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request))); + + final Service service = WebUtils.getService(this.argumentExtractors, + context); + + if (service != null && logger.isDebugEnabled()) { + logger.debug("Placing service in FlowScope: " + service.getId()); + } + + context.getFlowScope().put("service", 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; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/SendTicketGrantingTicketAction.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/SendTicketGrantingTicketAction.java new file mode 100644 index 0000000..361def2 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/SendTicketGrantingTicketAction.java @@ -0,0 +1,76 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import org.jasig.cas.CentralAuthenticationService; +import org.jasig.cas.web.support.CookieRetrievingCookieGenerator; +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.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 + * @version $Revision$ $Date$ + * @since 3.0.4 + */ +public final class SendTicketGrantingTicketAction extends AbstractAction { + + @NotNull + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + /** Instance of CentralAuthenticationService. */ + @NotNull + private CentralAuthenticationService centralAuthenticationService; + + 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(); + } + + this.ticketGrantingTicketCookieGenerator.addCookie(WebUtils.getHttpServletRequest(context), WebUtils + .getHttpServletResponse(context), ticketGrantingTicketId); + + if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) { + this.centralAuthenticationService + .destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie); + } + + return success(); + } + + public void setTicketGrantingTicketCookieGenerator(final CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator) { + this.ticketGrantingTicketCookieGenerator= ticketGrantingTicketCookieGenerator; + } + + public void setCentralAuthenticationService( + final CentralAuthenticationService centralAuthenticationService) { + this.centralAuthenticationService = centralAuthenticationService; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/TerminateWebSessionListener.java b/cas-server-core/src/main/java/org/jasig/cas/web/flow/TerminateWebSessionListener.java new file mode 100644 index 0000000..e1d8f12 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/flow/TerminateWebSessionListener.java @@ -0,0 +1,108 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.web.support.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.webflow.core.collection.AttributeMap; +import org.springframework.webflow.execution.FlowExecutionListenerAdapter; +import org.springframework.webflow.execution.FlowSession; +import org.springframework.webflow.execution.RequestContext; + +/** + * Listener to expire web session as soon as the webflow is ended. The goal is to decrease memory consumption by deleting as soon as + * possible the web sessions created mainly for login process. + * + * @author Jerome Leleu + * @author Marvin S. Addison + * @since 3.5.1 + */ +public final class TerminateWebSessionListener extends FlowExecutionListenerAdapter { + + /** Session marker that if present indicates a session that should not be terminated by this component. */ + private static final String DO_NOT_TERMINATE = TerminateWebSessionListener.class + ".DO_NOT_TERMINATE"; + + private static final Logger logger = LoggerFactory.getLogger(TerminateWebSessionListener.class); + + @Min(0) + private int timeToDieInSeconds = 2; + + /** URL to service manager Web application. */ + @NotNull + private String serviceManagerUrl; + + @Override + public void sessionStarted(final RequestContext context, final FlowSession session) { + final Service service; + // Guard against exceptions that arise from attempts to access terminated flow sessions + try { + service = WebUtils.getService(context); + } catch (final IllegalStateException e) { + logger.debug("Error getting service from flow state.", e); + return; + } + // If the user has requested a ticket for the service manager application + // then tag the session so it is not terminated. + if (service != null && service.getId().startsWith(serviceManagerUrl)) { + final HttpSession webSession = WebUtils.getHttpServletRequest(context).getSession(false); + if (webSession != null) { + webSession.setAttribute(DO_NOT_TERMINATE, true); + } + } + } + + @Override + public void sessionEnded(final RequestContext context, final FlowSession session, final String outcome, + final AttributeMap output) { + + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + // get session but don't create it if it doesn't already exist + final HttpSession webSession = request.getSession(false); + + if (webSession != null && webSession.getAttribute(DO_NOT_TERMINATE) == null) { + logger.debug("Terminate web session {} in {} seconds", webSession.getId(), this.timeToDieInSeconds); + // set the web session to die in timeToDieInSeconds + webSession.setMaxInactiveInterval(this.timeToDieInSeconds); + } + } + + public int getTimeToDieInSeconds() { + return this.timeToDieInSeconds; + } + + public void setTimeToDieInSeconds(final int timeToDieInSeconds) { + this.timeToDieInSeconds = timeToDieInSeconds; + } + + /** + * Sets the URL to the service manager Web application. + * + * @param url URL to service manager. + */ + public void setServiceManagerUrl(final String url) { + this.serviceManagerUrl = url; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/flow/package.html b/cas-server-core/src/main/java/org/jasig/cas/web/flow/package.html new file mode 100644 index 0000000..21ca7f7 --- /dev/null +++ b/cas-server-core/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-core/src/main/java/org/jasig/cas/web/init/SafeContextLoaderListener.java b/cas-server-core/src/main/java/org/jasig/cas/web/init/SafeContextLoaderListener.java new file mode 100644 index 0000000..cddaf34 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/init/SafeContextLoaderListener.java @@ -0,0 +1,112 @@ +/* + * 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. + */ +package org.jasig.cas.web.init; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.context.ContextLoaderListener; + +/** + * A {@link ServletContextListener} which wraps Spring's + * {@link ContextLoaderListener} and catches anything that delegate throws so as + * to prevent its having thrown a Throwable from aborting the initialization of + * our entire web application context. Use of this ContextListener will not be + * appropriate for all deployments of a web application. Sometimes, a context + * listener's aborting context initialization is exactly the desired behavior. + * This might be because the resulting application inavailability is acceptable + * or because another layer (Apache, a proxy, a balancer, etc.) detects the + * inavailability of the context and provides an appropriate user experience. Or + * because a root context handles all requests that aren't otherwise handled. + * There are many fine alternatives to this approach. However, when using the + * bare Tomcat container with CAS as the root context, if your desired behavior + * is that a failure at context initialization results in a dummy CAS context + * that presents a user-friendly "CAS is unavailable at this time" message, + * using this ContextListener in place of Spring's {@link ContextLoaderListener} + * should do the trick. The error page associated with this deployment failure + * is configured in the web.xml via the standard error handling mechanism. + * Rather than being a generic ContextListener wrapper that will make safe any + * ContextListener, instead this implementation specifically wraps and delegates + * to a Spring {@link ContextLoaderListener}. As such, mapping this listener in + * web.xml is a one for one replacement for {@link ContextLoaderListener}. + *

+ * The exception thrown is exposed in the Servlet Context under the key + * "exceptionCaughtByListener". + * + * @author Andrew Petro + * @version $Revision$ $Date$ + * @see ContextLoaderListener + */ +public final class SafeContextLoaderListener implements ServletContextListener { + + /** Instance of Commons Logging. */ + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** + * The name of the ServletContext attribute whereat we will place a List of + * Throwables that we caught from our delegate context listeners. + */ + public static final String CAUGHT_THROWABLE_KEY = "exceptionCaughtByListener"; + + /** The actual ContextLoaderListener to which we will delegate to. */ + private final ContextLoaderListener delegate = new ContextLoaderListener(); + + public void contextInitialized(final ServletContextEvent sce) { + try { + this.delegate.contextInitialized(sce); + } catch (Throwable t) { + /* + * no matter what went wrong, our role is to capture this error and + * prevent it from blocking initialization of the context. logging + * overkill so that our deployer will find a record of this problem + * even if unfamiliar with Commons Logging and properly configuring + * it. + */ + + final String message = "SafeContextLoaderListener: \n" + + "The Spring ContextLoaderListener we wrap threw on contextInitialized.\n" + + "But for our having caught this error, the web application context would not have initialized."; + + // log it via Commons Logging + log.error(message, t); + + // log it to System.err + System.err.println(message); + t.printStackTrace(); + + // log it to the ServletContext + ServletContext context = sce.getServletContext(); + context.log(message, t); + + /* + * record the error so that the application has access to later + * display a proper error message based on the exception. + */ + context.setAttribute(CAUGHT_THROWABLE_KEY, t); + } + } + + public void contextDestroyed(final ServletContextEvent sce) { + this.delegate.contextDestroyed(sce); + } + +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/init/SafeDispatcherServlet.java b/cas-server-core/src/main/java/org/jasig/cas/web/init/SafeDispatcherServlet.java new file mode 100644 index 0000000..7227f8e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/init/SafeDispatcherServlet.java @@ -0,0 +1,134 @@ +/* + * 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. + */ +package org.jasig.cas.web.init; + +import java.io.IOException; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContextException; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * This servlet wraps the Spring DispatchServlet, catching any exceptions it + * throws on init() to guarantee that servlet initialization succeeds. This + * allows our application context to succeed in initializing so that we can + * display a friendly "CAS is not available" page to the deployer (an + * appropriate use of the page in development) or to the end user (an + * appropriate use of the page in production). The error page associated with + * this deployment failure is configured in the web.xml via the standard error + * handling mechanism. + *

+ * If the underlying Spring DispatcherServlet failed to init(), this + * SafeDispatcherServlet will throw an + * org.springframework.context.ApplicationContextException on + * service(). + *

+ * The exception thrown by the underlying Spring DispatcherServlet init() and + * caught in the SafeDispatcherServlet init() is exposed as a Servlet Context + * attribute under the key "exceptionCaughtByServlet". + * + * @author Andrew Petro + * @version $Revision$ $Date$ + * @see DispatcherServlet + */ +public final class SafeDispatcherServlet extends HttpServlet { + + /** Unique Id for serialization. */ + private static final long serialVersionUID = 1L; + + /** Key under which we will store the exception in the ServletContext. */ + public static final String CAUGHT_THROWABLE_KEY = "exceptionCaughtByServlet"; + + /** Instance of Commons Logging. */ + private static final Logger log = LoggerFactory.getLogger(SafeDispatcherServlet.class); + + /** The actual DispatcherServlet to which we will delegate to. */ + private DispatcherServlet delegate = new DispatcherServlet(); + + /** Boolean to determine if the application deployed successfully. */ + private boolean initSuccess = true; + + public void init(final ServletConfig config) { + try { + this.delegate.init(config); + + } catch (final Throwable t) { + // let the service method know initialization failed. + this.initSuccess = false; + + /* + * no matter what went wrong, our role is to capture this error and + * prevent it from blocking initialization of the servlet. logging + * overkill so that our deployer will find a record of this problem + * even if unfamiliar with Commons Logging and properly configuring + * it. + */ + + final String message = "SafeDispatcherServlet: \n" + + "The Spring DispatcherServlet we wrap threw on init.\n" + + "But for our having caught this error, the servlet would not have initialized."; + + // log it via Commons Logging + log.error(message, t); + + // log it to System.err + System.err.println(message); + t.printStackTrace(); + + // log it to the ServletContext + ServletContext context = config.getServletContext(); + context.log(message, t); + + /* + * record the error so that the application has access to later + * display a proper error message based on the exception. + */ + context.setAttribute(CAUGHT_THROWABLE_KEY, t); + + } + } + + /** + * @throws ApplicationContextException if the DispatcherServlet does not + * initialize properly, but the servlet attempts to process a request. + */ + public void service(final ServletRequest req, final ServletResponse resp) + throws ServletException, IOException { + /* + * Since our container calls only this method and not any of the other + * HttpServlet runtime methods, such as doDelete(), etc., delegating + * this method is sufficient to delegate all of the methods in the + * HttpServlet API. + */ + if (this.initSuccess) { + this.delegate.service(req, resp); + } else { + throw new ApplicationContextException( + "Unable to initialize application context."); + } + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/init/package.html b/cas-server-core/src/main/java/org/jasig/cas/web/init/package.html new file mode 100644 index 0000000..439e2b0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/init/package.html @@ -0,0 +1,32 @@ + + + +

This package is concerned with the deployment of CAS regardless of +configuration exceptions. The default Spring behavior is to fail fast on +deployment if there is an initialization exception. The classes in this +package override that ability. The application will deploy regardless. +However, when a user attempts to access the system, they will be +presented with an error message regarding access to the application.

+

These classes are useful in the case where Tomcat is not fronted by +Apache.

+ + diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/package.html b/cas-server-core/src/main/java/org/jasig/cas/web/package.html new file mode 100644 index 0000000..3712c0e --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/package.html @@ -0,0 +1,58 @@ + + + + +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-core/src/main/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java new file mode 100644 index 0000000..7758bff --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java @@ -0,0 +1,92 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import java.util.Date; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.servlet.http.HttpServletRequest; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0.5 + */ +public abstract class AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter extends AbstractThrottledSubmissionHandlerInterceptorAdapter { + + 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()); + } + + 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.keySet(); + log.debug("Decrementing counts for throttler. Starting key count: " + keys.size()); + + final Date now = new Date(); + String key; + for (final Iterator iter = keys.iterator(); iter.hasNext();) { + key = iter.next(); + if (submissionRate(now, this.ipMap.get(key)) < getThresholdRate()) { + log.trace("Removing entry for key {}", key); + iter.remove(); + } + } + log.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 1000.0 / (a.getTime() - b.getTime()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractSingleSignOutEnabledArgumentExtractor.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractSingleSignOutEnabledArgumentExtractor.java new file mode 100644 index 0000000..9974ec0 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractSingleSignOutEnabledArgumentExtractor.java @@ -0,0 +1,74 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.util.HttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for handling the enabling and disabling of Single Sign Out features. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1.2 + * + */ +public abstract class AbstractSingleSignOutEnabledArgumentExtractor implements + ArgumentExtractor { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + /** Whether single sign out is disabled or not. */ + private boolean disableSingleSignOut = false; + + /** Default instance of HttpClient. */ + @NotNull + private HttpClient httpClient; + + public void setHttpClient(final HttpClient httpClient) { + this.httpClient = httpClient; + } + + protected HttpClient getHttpClientIfSingleSignOutEnabled() { + return !this.disableSingleSignOut ? this.httpClient : null; + } + + public void setDisableSingleSignOut(final boolean disableSingleSignOut) { + this.disableSingleSignOut = disableSingleSignOut; + } + + public final WebApplicationService extractService(final HttpServletRequest request) { + final WebApplicationService service = extractServiceInternal(request); + + if (service == null) { + log.debug("Extractor did not generate service."); + } else { + log.debug("Extractor generated service for: " + service.getId()); + } + + return service; + } + + protected abstract WebApplicationService extractServiceInternal(final HttpServletRequest request); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapter.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapter.java new file mode 100644 index 0000000..64a0bdb --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapter.java @@ -0,0 +1,142 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +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 + * @version $Revision$ $Date$ + * @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"; + + protected final Logger log = 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; + + + 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); + response.sendError(403, "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; + } + + 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; + } + + protected void recordThrottle(final HttpServletRequest request) { + log.warn("Throttling submission from {}. More than {} failed login attempts within {} seconds.", + new Object[] {request.getRemoteAddr(), failureThreshold, failureRangeInSeconds}); + } + + protected abstract void recordSubmissionFailure(HttpServletRequest request); + + protected abstract boolean exceedsThreshold(HttpServletRequest request); +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/ArgumentExtractor.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/ArgumentExtractor.java new file mode 100644 index 0000000..0e2f627 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/ArgumentExtractor.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.WebApplicationService; + +/** + * Strategy interface for retrieving services and tickets from the request. + *

+ * These are the two things the CAS protocol and the SAML protocol have in + * common. + * + * @author Scott Battatglia + * @version $Revision$ $Date$ + * @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/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 0000000..7a05567 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/CasArgumentExtractor.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +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. Accepts an HttpClient reference. A default + * one is configured that you can override. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class CasArgumentExtractor extends AbstractSingleSignOutEnabledArgumentExtractor { + + public final WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return SimpleWebApplicationServiceImpl.createServiceFrom(request, getHttpClientIfSingleSignOutEnabled()); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/CookieRetrievingCookieGenerator.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/CookieRetrievingCookieGenerator.java new file mode 100644 index 0000000..db75f77 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/CookieRetrievingCookieGenerator.java @@ -0,0 +1,69 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jasig.cas.authentication.principal.RememberMeCredentials; +import org.springframework.util.StringUtils; +import org.springframework.web.util.CookieGenerator; + +/** + * Extends CookieGenerator to allow you to retrieve a value from a request. + *

+ * Also has support for RememberMe Services + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class CookieRetrievingCookieGenerator extends CookieGenerator { + + /** The maximum age the cookie should be remembered for. + * The default is three months (7889231 in seconds, according to Google) */ + private int rememberMeMaxAge = 7889231; + + public void addCookie(final HttpServletRequest request, final HttpServletResponse response, final String cookieValue) { + + if (!StringUtils.hasText(request.getParameter(RememberMeCredentials.REQUEST_PARAMETER_REMEMBER_ME))) { + super.addCookie(response, cookieValue); + } else { + final Cookie cookie = createCookie(cookieValue); + cookie.setMaxAge(this.rememberMeMaxAge); + if (isCookieSecure()) { + cookie.setSecure(true); + } + response.addCookie(cookie); + } + } + + public String retrieveCookieValue(final HttpServletRequest request) { + final Cookie cookie = org.springframework.web.util.WebUtils.getCookie( + request, getCookieName()); + + return cookie == null ? null : cookie.getValue(); + } + + public void setRememberMeMaxAge(final int maxAge) { + this.rememberMeMaxAge = maxAge; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/GoogleAccountsArgumentExtractor.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/GoogleAccountsArgumentExtractor.java new file mode 100644 index 0000000..9cd6c5f --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/GoogleAccountsArgumentExtractor.java @@ -0,0 +1,72 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.principal.GoogleAccountsService; +import org.jasig.cas.authentication.principal.WebApplicationService; + +/** + * Constructs a GoogleAccounts compatible service and provides the public and + * private keys. + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public final class GoogleAccountsArgumentExtractor extends AbstractSingleSignOutEnabledArgumentExtractor { + + @NotNull + private PublicKey publicKey; + + @NotNull + private PrivateKey privateKey; + + private String alternateUsername; + + public WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return GoogleAccountsService.createServiceFrom(request, + this.privateKey, this.publicKey, this.alternateUsername); + } + + public void setPrivateKey(final PrivateKey privateKey) { + this.privateKey = privateKey; + } + + public void setPublicKey(final PublicKey publicKey) { + this.publicKey = publicKey; + } + + /** + * 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. + */ + public void setAlternateUsername(final String alternateUsername) { + this.alternateUsername = alternateUsername; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java new file mode 100644 index 0000000..ad83d41 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +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 + * @version $Revision$ $Date$ + * @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 request.getRemoteAddr() + ";" + username.toLowerCase(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter.java new file mode 100644 index 0000000..94ab898 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter.java @@ -0,0 +1,37 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +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 + * @version $Revision$ $Date$ + * @since 3.3.5 + */ +public final class InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter extends AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter { + + @Override + protected String constructKey(final HttpServletRequest request) { + return request.getRemoteAddr(); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java new file mode 100644 index 0000000..be60286 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter.java @@ -0,0 +1,136 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import javax.servlet.http.HttpServletRequest; +import javax.sql.DataSource; + +import com.github.inspektr.audit.AuditActionContext; +import com.github.inspektr.audit.AuditPointRuntimeInfo; +import com.github.inspektr.audit.AuditTrailManager; +import com.github.inspektr.common.web.ClientInfo; +import com.github.inspektr.common.web.ClientInfoHolder; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; + +/** + * 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 + * @version $Revision$ $Date$ + * @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 final AuditTrailManager auditTrailManager; + + private final JdbcTemplate jdbcTemplate; + + private String applicationCode = DEFAULT_APPLICATION_CODE; + + private String authenticationFailureCode = DEFAULT_AUTHN_FAILED_ACTION; + + 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() { + public Timestamp mapRow(ResultSet resultSet, 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 1000.0 / (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() { + 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; + } + + protected String constructUsername(HttpServletRequest request, String usernameParameter) { + final String username = request.getParameter(usernameParameter); + return "[username: " + (username != null ? username : "") + "]"; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/SamlArgumentExtractor.java b/cas-server-core/src/main/java/org/jasig/cas/web/support/SamlArgumentExtractor.java new file mode 100644 index 0000000..3c3e014 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/SamlArgumentExtractor.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.authentication.principal.SamlService; +import org.jasig.cas.authentication.principal.WebApplicationService; + +/** + * Retrieve the ticket and artifact based on the SAML 1.1 profile. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class SamlArgumentExtractor extends AbstractSingleSignOutEnabledArgumentExtractor { + + public WebApplicationService extractServiceInternal(final HttpServletRequest request) { + return SamlService.createServiceFrom(request, getHttpClientIfSingleSignOutEnabled()); + } +} 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 0000000..f3a34a9 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/WebUtils.java @@ -0,0 +1,128 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.springframework.util.Assert; +import org.springframework.webflow.context.servlet.ServletExternalContext; +import org.springframework.webflow.execution.RequestContext; + +/** + * Common utilities for the web tier. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @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"; + + 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(); + } + + 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(); + } + + 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; + } + + public static WebApplicationService getService( + final List argumentExtractors, + final RequestContext context) { + final HttpServletRequest request = WebUtils.getHttpServletRequest(context); + return getService(argumentExtractors, request); + } + + public static WebApplicationService getService( + final RequestContext context) { + return (WebApplicationService) context.getFlowScope().get("service"); + } + + public static void putTicketGrantingTicketInRequestScope( + final RequestContext context, final String ticketValue) { + context.getRequestScope().put("ticketGrantingTicketId", ticketValue); + } + + public static String getTicketGrantingTicketId( + final RequestContext context) { + final String tgtFromRequest = (String) context.getRequestScope().get("ticketGrantingTicketId"); + final String tgtFromFlow = (String) context.getFlowScope().get("ticketGrantingTicketId"); + + return tgtFromRequest != null ? tgtFromRequest : tgtFromFlow; + + } + + public static void putServiceTicketInRequestScope( + final RequestContext context, final String ticketValue) { + context.getRequestScope().put("serviceTicketId", ticketValue); + } + + public static String getServiceTicketFromRequestScope( + final RequestContext context) { + return context.getRequestScope().getString("serviceTicketId"); + } + + public static void putLoginTicket(final RequestContext context, final String ticket) { + context.getFlowScope().put("loginTicket", ticket); + } + + 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 : ""; + } + + public static String getLoginTicketFromRequest(final RequestContext context) { + return context.getRequestParameters().get("lt"); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/support/package.html b/cas-server-core/src/main/java/org/jasig/cas/web/support/package.html new file mode 100644 index 0000000..4deb838 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/support/package.html @@ -0,0 +1,25 @@ + + + +

Classes related to assisting in management of the web tier.

+ + 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 0000000..58e2f13 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractCasView.java @@ -0,0 +1,42 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +import java.util.Map; + +import org.jasig.cas.validation.Assertion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.servlet.view.AbstractView; + +/** + * Abstract class to handle retrieving the Assertion from the model. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractCasView extends AbstractView { + + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + + protected final Assertion getAssertionFrom(final Map model) { + return (Assertion) model.get("assertion"); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractSaml10ResponseView.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractSaml10ResponseView.java new file mode 100644 index 0000000..43cf010 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/AbstractSaml10ResponseView.java @@ -0,0 +1,174 @@ +/* + * 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. + */ + +package org.jasig.cas.web.view; + +import java.lang.reflect.Field; +import java.security.NoSuchAlgorithmException; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import javax.xml.namespace.QName; + +import org.jasig.cas.authentication.principal.SamlService; +import org.jasig.cas.authentication.principal.WebApplicationService; +import org.jasig.cas.util.CasHTTPSOAP11Encoder; +import org.jasig.cas.web.support.SamlArgumentExtractor; +import org.joda.time.DateTime; +import org.opensaml.Configuration; +import org.opensaml.DefaultBootstrap; +import org.opensaml.common.SAMLObject; +import org.opensaml.common.SAMLObjectBuilder; +import org.opensaml.common.SAMLVersion; +import org.opensaml.common.binding.BasicSAMLMessageContext; +import org.opensaml.common.impl.SecureRandomIdentifierGenerator; +import org.opensaml.saml1.binding.encoding.HTTPSOAP11Encoder; +import org.opensaml.saml1.core.Response; +import org.opensaml.saml1.core.Status; +import org.opensaml.saml1.core.StatusCode; +import org.opensaml.saml1.core.StatusMessage; +import org.opensaml.ws.transport.http.HttpServletResponseAdapter; +import org.opensaml.xml.ConfigurationException; + +/** + * 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_ELEMENT_NAME_FIELD = "DEFAULT_ELEMENT_NAME"; + + private static final String DEFAULT_ENCODING = "UTF-8"; + + private final SamlArgumentExtractor samlArgumentExtractor = new SamlArgumentExtractor(); + + private final HTTPSOAP11Encoder encoder = new CasHTTPSOAP11Encoder(); + + private final SecureRandomIdentifierGenerator idGenerator; + + @NotNull + private String encoding = DEFAULT_ENCODING; + + /** + * Sets the character encoding in the HTTP response. + * + * @param encoding Response character encoding. + */ + public void setEncoding(final String encoding) { + this.encoding = encoding; + } + + static { + try { + // Initialize OpenSAML default configuration + // (only needed once per classloader) + DefaultBootstrap.bootstrap(); + } catch (final ConfigurationException e) { + throw new IllegalStateException("Error initializing OpenSAML library.", e); + } + } + + protected AbstractSaml10ResponseView() { + try { + this.idGenerator = new SecureRandomIdentifierGenerator(); + } catch (final NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot create secure random ID generator for SAML message IDs."); + } + } + + 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 = newSamlObject(Response.class); + samlResponse.setID(generateId()); + samlResponse.setIssueInstant(new DateTime()); + samlResponse.setVersion(SAMLVersion.VERSION_11); + samlResponse.setRecipient(serviceId); + if (service instanceof SamlService) { + final SamlService samlService = (SamlService) service; + + if (samlService.getRequestID() != null) { + samlResponse.setInResponseTo(samlService.getRequestID()); + } + } + prepareResponse(samlResponse, model); + + final BasicSAMLMessageContext messageContext = new BasicSAMLMessageContext(); + messageContext.setOutboundMessageTransport(new HttpServletResponseAdapter(response, request.isSecure())); + messageContext.setOutboundSAMLMessage(samlResponse); + this.encoder.encode(messageContext); + } catch (final Exception e) { + this.log.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); + + + protected final String generateId() { + return this.idGenerator.generateIdentifier(); + } + + protected final T newSamlObject(final Class objectType) { + final QName qName; + try { + final Field f = objectType.getField(DEFAULT_ELEMENT_NAME_FIELD); + qName = (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); + } + final SAMLObjectBuilder builder = (SAMLObjectBuilder) Configuration.getBuilderFactory().getBuilder(qName); + if (builder == null) { + throw new IllegalStateException("No SAMLObjectBuilder registered for class " + objectType.getName()); + } + return objectType.cast(builder.buildObject()); + } + + protected final 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; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/Cas10ResponseView.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/Cas10ResponseView.java new file mode 100644 index 0000000..38734b7 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/Cas10ResponseView.java @@ -0,0 +1,63 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jasig.cas.validation.Assertion; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class Cas10ResponseView extends AbstractCasView { + + /** + * Indicate whether this view will be generating the success response or + * not. + */ + private boolean successResponse; + + protected void renderMergedOutputModel(final Map model, + final HttpServletRequest request, final HttpServletResponse response) + throws Exception { + final Assertion assertion = getAssertionFrom(model); + + if (this.successResponse) { + response.getWriter().print( + "yes\n" + + assertion.getChainedAuthentications().get(0).getPrincipal() + .getId() + "\n"); + } else { + response.getWriter().print("no\n\n"); + } + } + + public void setSuccessResponse(final boolean successResponse) { + this.successResponse = successResponse; + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/Saml10FailureResponseView.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/Saml10FailureResponseView.java new file mode 100644 index 0000000..580c255 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/Saml10FailureResponseView.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +import java.util.Map; + +import org.opensaml.saml1.core.Response; +import org.opensaml.saml1.core.StatusCode; + +/** + * 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(newStatus(StatusCode.REQUEST_DENIED, (String) model.get("description"))); + } +} diff --git a/cas-server-core/src/main/java/org/jasig/cas/web/view/Saml10SuccessResponseView.java b/cas-server-core/src/main/java/org/jasig/cas/web/view/Saml10SuccessResponseView.java new file mode 100644 index 0000000..df9f603 --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/Saml10SuccessResponseView.java @@ -0,0 +1,209 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotNull; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.SamlAuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.principal.RememberMeCredentials; +import org.jasig.cas.authentication.principal.Service; +import org.joda.time.DateTime; +import org.opensaml.saml1.core.Assertion; +import org.opensaml.saml1.core.Attribute; +import org.opensaml.saml1.core.AttributeStatement; +import org.opensaml.saml1.core.AttributeValue; +import org.opensaml.saml1.core.Audience; +import org.opensaml.saml1.core.AudienceRestrictionCondition; +import org.opensaml.saml1.core.AuthenticationStatement; +import org.opensaml.saml1.core.Conditions; +import org.opensaml.saml1.core.ConfirmationMethod; +import org.opensaml.saml1.core.NameIdentifier; +import org.opensaml.saml1.core.Response; +import org.opensaml.saml1.core.StatusCode; +import org.opensaml.saml1.core.Subject; +import org.opensaml.saml1.core.SubjectConfirmation; +import org.opensaml.xml.schema.XSString; +import org.opensaml.xml.schema.impl.XSStringBuilder; + +/** + * 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. + *

+ * Note: This class currently expects a bean called "ServiceRegistry" to exist. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + */ +public final class Saml10SuccessResponseView extends AbstractSaml10ResponseView { + + /** Namespace for custom attributes. */ + private static final String NAMESPACE = "http://www.ja-sig.org/products/cas/"; + + private static final String REMEMBER_ME_ATTRIBUTE_NAME = "longTermAuthenticationRequestTokenUsed"; + + private static final String REMEMBER_ME_ATTRIBUTE_VALUE = "true"; + + private static final String CONFIRMATION_METHOD = "urn:oasis:names:tc:SAML:1.0:cm:artifact"; + + private final XSStringBuilder attrValueBuilder = new XSStringBuilder(); + + /** The issuer, generally the hostname. */ + @NotNull + private String issuer; + + /** The amount of time in milliseconds this is valid for. */ + @Min(1000) + private long issueLength = 30000; + + @NotNull + private String rememberMeAttributeName = REMEMBER_ME_ATTRIBUTE_NAME; + + @Override + protected void prepareResponse(final Response response, final Map model) { + final Authentication authentication = getAssertionFrom(model).getChainedAuthentications().get(0); + final DateTime issuedAt = response.getIssueInstant(); + final Service service = getAssertionFrom(model).getService(); + final boolean isRemembered = ( + authentication.getAttributes().get(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME) == Boolean.TRUE + && !getAssertionFrom(model).isFromNewLogin()); + + // Build up the SAML assertion containing AuthenticationStatement and AttributeStatement + final Assertion assertion = newSamlObject(Assertion.class); + assertion.setID(generateId()); + assertion.setIssueInstant(issuedAt); + assertion.setIssuer(this.issuer); + assertion.setConditions(newConditions(issuedAt, service.getId())); + final AuthenticationStatement authnStatement = newAuthenticationStatement(authentication); + assertion.getAuthenticationStatements().add(authnStatement); + final Map attributes = authentication.getPrincipal().getAttributes(); + if (!attributes.isEmpty() || isRemembered) { + assertion.getAttributeStatements().add( + newAttributeStatement(newSubject(authentication.getPrincipal().getId()), attributes, isRemembered)); + } + response.setStatus(newStatus(StatusCode.SUCCESS, null)); + response.getAssertions().add(assertion); + } + + private Conditions newConditions(final DateTime issuedAt, final String serviceId) { + final Conditions conditions = newSamlObject(Conditions.class); + conditions.setNotBefore(issuedAt); + conditions.setNotOnOrAfter(issuedAt.plus(this.issueLength)); + final AudienceRestrictionCondition audienceRestriction = newSamlObject(AudienceRestrictionCondition.class); + final Audience audience = newSamlObject(Audience.class); + audience.setUri(serviceId); + audienceRestriction.getAudiences().add(audience); + conditions.getAudienceRestrictionConditions().add(audienceRestriction); + return conditions; + } + + private Subject newSubject(final String identifier) { + final SubjectConfirmation confirmation = newSamlObject(SubjectConfirmation.class); + final ConfirmationMethod method = newSamlObject(ConfirmationMethod.class); + method.setConfirmationMethod(CONFIRMATION_METHOD); + 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; + } + + private AuthenticationStatement newAuthenticationStatement(final Authentication authentication) { + final String authenticationMethod = (String) authentication.getAttributes().get( + SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD); + final AuthenticationStatement authnStatement = newSamlObject(AuthenticationStatement.class); + authnStatement.setAuthenticationInstant(new DateTime(authentication.getAuthenticatedDate())); + authnStatement.setAuthenticationMethod( + authenticationMethod != null + ? authenticationMethod + : SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_UNSPECIFIED); + authnStatement.setSubject(newSubject(authentication.getPrincipal().getId())); + return authnStatement; + } + + private AttributeStatement newAttributeStatement( + final Subject subject, final Map attributes, final boolean isRemembered) { + + final AttributeStatement attrStatement = newSamlObject(AttributeStatement.class); + attrStatement.setSubject(subject); + for (final Entry e : attributes.entrySet()) { + if (e.getValue() instanceof Collection && ((Collection) e.getValue()).isEmpty()) { + // bnoordhuis: don't add the attribute, it causes a org.opensaml.MalformedException + log.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(NAMESPACE); + if (e.getValue() instanceof Collection) { + final Collection c = (Collection) e.getValue(); + for (final Object value : c) { + attribute.getAttributeValues().add(newAttributeValue(value)); + } + } else { + attribute.getAttributeValues().add(newAttributeValue(e.getValue())); + } + attrStatement.getAttributes().add(attribute); + } + + if (isRemembered) { + final Attribute attribute = newSamlObject(Attribute.class); + attribute.setAttributeName(this.rememberMeAttributeName); + attribute.setAttributeNamespace(NAMESPACE); + attribute.getAttributeValues().add(newAttributeValue(REMEMBER_ME_ATTRIBUTE_VALUE)); + attrStatement.getAttributes().add(attribute); + } + return attrStatement; + } + + private XSString newAttributeValue(final Object value) { + final XSString stringValue = this.attrValueBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME); + if (value instanceof String) { + stringValue.setValue((String) value); + } else { + stringValue.setValue(value.toString()); + } + return stringValue; + } + + 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-core/src/main/java/org/jasig/cas/web/view/package.html b/cas-server-core/src/main/java/org/jasig/cas/web/view/package.html new file mode 100644 index 0000000..a633c9c --- /dev/null +++ b/cas-server-core/src/main/java/org/jasig/cas/web/view/package.html @@ -0,0 +1,25 @@ + + + +

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

+ + 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 0000000..c9e6c13 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/AbstractCentralAuthenticationServiceTest.java @@ -0,0 +1,59 @@ +/* + * 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. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.ticket.registry.TicketRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +@ContextConfiguration(locations={"/applicationContext.xml"}) +public abstract class AbstractCentralAuthenticationServiceTest extends AbstractJUnit4SpringContextTests { + + @Autowired(required = true) + private CentralAuthenticationService centralAuthenticationService; + + @Autowired(required=true) + private TicketRegistry ticketRegistry; + + @Autowired(required=true) + private AuthenticationManager authenticationManager; + + 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; + } +} 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 0000000..e8c7715 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplTests.java @@ -0,0 +1,368 @@ +/* + * 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. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.jasig.cas.services.UnauthorizedServiceException; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketException; +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.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class CentralAuthenticationServiceImplTests extends AbstractCentralAuthenticationServiceTest { + + @Test + public void testBadCredentialsOnTicketGrantingTicketCreation() { + try { + getCentralAuthenticationService().createTicketGrantingTicket( + TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testGoodCredentialsOnTicketGrantingTicketCreation() { + try { + assertNotNull(getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword())); + } catch (TicketException e) { + fail(TestUtils.CONST_EXCEPTION_NON_EXPECTED); + } + } + + @Test + public void testDestroyTicketGrantingTicketWithNonExistantTicket() { + getCentralAuthenticationService().destroyTicketGrantingTicket("test"); + } + + @Test + public void testDestroyTicketGrantingTicketWithValidTicket() + throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().destroyTicketGrantingTicket(ticketId); + } + + @Test + public void testDestroyTicketGrantingTicketWithInvalidTicket() + throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId, TestUtils.getService()); + try { + getCentralAuthenticationService().destroyTicketGrantingTicket( + serviceTicketId); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (ClassCastException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testGrantServiceTicketWithValidTicketGrantingTicket() + throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket(ticketId, + TestUtils.getService()); + } + + @Test + public void testGrantServiceTicketWithInvalidTicketGrantingTicket() + throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().destroyTicketGrantingTicket(ticketId); + try { + getCentralAuthenticationService().grantServiceTicket(ticketId, + TestUtils.getService()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testGrantServiceTicketWithExpiredTicketGrantingTicket() throws TicketException { + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()).setTicketGrantingTicketExpirationPolicy(new ExpirationPolicy() { + private static final long serialVersionUID = 1L; + + public boolean isExpired(final TicketState ticket) { + return true; + }}); + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + try { + getCentralAuthenticationService().grantServiceTicket(ticketId, + TestUtils.getService()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } finally { + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()).setTicketGrantingTicketExpirationPolicy(new NeverExpiresExpirationPolicy()); + } +} + + @Test + public void testDelegateTicketGrantingTicketWithProperParams() throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId, TestUtils.getService()); + getCentralAuthenticationService().delegateTicketGrantingTicket( + serviceTicketId, TestUtils.getHttpBasedServiceCredentials()); + } + + @Test + public void testDelegateTicketGrantingTicketWithBadCredentials() + throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId, TestUtils.getService()); + try { + getCentralAuthenticationService().delegateTicketGrantingTicket( + serviceTicketId, TestUtils.getBadHttpBasedServiceCredentials()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testDelegateTicketGrantingTicketWithBadServiceTicket() + throws TicketException { + final String ticketId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicketId = getCentralAuthenticationService() + .grantServiceTicket(ticketId, TestUtils.getService()); + getCentralAuthenticationService().destroyTicketGrantingTicket(ticketId); + try { + getCentralAuthenticationService().delegateTicketGrantingTicket( + serviceTicketId, TestUtils.getHttpBasedServiceCredentials()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testGrantServiceTicketWithValidCredentials() + throws TicketException { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket( + ticketGrantingTicket, TestUtils.getService(), + TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test + public void testGrantServiceTicketWithInvalidCredentials() + throws TicketException { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + try { + getCentralAuthenticationService().grantServiceTicket( + ticketGrantingTicket, TestUtils.getService(), + TestUtils.getBadHttpBasedServiceCredentials()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testGrantServiceTicketWithDifferentCredentials() + throws TicketException { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + try { + getCentralAuthenticationService().grantServiceTicket( + ticketGrantingTicket, TestUtils.getService(), + TestUtils.getCredentialsWithSameUsernameAndPassword("test1")); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testValidateServiceTicketWithExpires() throws TicketException { + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()) + .setServiceTicketExpirationPolicy(new MultiTimeUseOrTimeoutExpirationPolicy( + 1, 1100)); + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + + getCentralAuthenticationService().validateServiceTicket(serviceTicket, + TestUtils.getService()); + + assertFalse(getTicketRegistry().deleteTicket(serviceTicket)); + ((CentralAuthenticationServiceImpl) getCentralAuthenticationService()) + .setServiceTicketExpirationPolicy(new NeverExpiresExpirationPolicy()); + } + + @Test + public void testValidateServiceTicketWithValidService() + throws TicketException { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + + getCentralAuthenticationService().validateServiceTicket(serviceTicket, + TestUtils.getService()); + } + + @Test + public void testValidateServiceTicketWithInvalidService() + throws TicketException { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + + try { + getCentralAuthenticationService().validateServiceTicket( + serviceTicket, TestUtils.getService("test2")); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (UnauthorizedServiceException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testValidateServiceTicketWithInvalidServiceTicket() + throws TicketException { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = getCentralAuthenticationService() + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + getCentralAuthenticationService().destroyTicketGrantingTicket( + ticketGrantingTicket); + + try { + getCentralAuthenticationService().validateServiceTicket( + serviceTicket, TestUtils.getService()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testValidateServiceTicketNonExistantTicket() { + try { + getCentralAuthenticationService().validateServiceTicket("test", + TestUtils.getService()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (TicketException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testValidateServiceTicketWithoutUsernameAttribute() throws TicketException { + UsernamePasswordCredentials cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final String ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + final String serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket, TestUtils.getService()); + final Authentication auth = assertion.getChainedAuthentications().get(0); + assertEquals(auth.getPrincipal().getId(), cred.getUsername()); + } + + @Test + public void testValidateServiceTicketWithDefaultUsernameAttribute() throws TicketException { + UsernamePasswordCredentials cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final String ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + Service svc = TestUtils.getService("testDefault"); + final String serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket, svc); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket, svc); + final Authentication auth = assertion.getChainedAuthentications().get(0); + assertEquals(auth.getPrincipal().getId(), cred.getUsername()); + } + + @Test + public void testValidateServiceTicketWithUsernameAttribute() throws TicketException { + UsernamePasswordCredentials cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final String ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + Service svc = TestUtils.getService("eduPersonTest"); + final String serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket, svc); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket, svc); + final Authentication auth = assertion.getChainedAuthentications().get(0); + assertEquals(auth.getPrincipal().getId(), "developer"); + } + + @Test + public void testValidateServiceTicketWithInvalidUsernameAttribute() throws TicketException { + UsernamePasswordCredentials cred = TestUtils.getCredentialsWithSameUsernameAndPassword(); + final String ticketGrantingTicket = getCentralAuthenticationService().createTicketGrantingTicket(cred); + + Service svc = TestUtils.getService("eduPersonTestInvalid"); + final String serviceTicket = getCentralAuthenticationService().grantServiceTicket(ticketGrantingTicket, svc); + + final Assertion assertion = getCentralAuthenticationService().validateServiceTicket(serviceTicket, svc); + final Authentication auth = assertion.getChainedAuthentications().get(0); + + /* + * The attribute specified for this service is not allows in the list of returned attributes. + * Therefore, we expect the default to be returned. + */ + assertEquals(auth.getPrincipal().getId(), cred.getUsername()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplWithMokitoTests.java b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplWithMokitoTests.java new file mode 100644 index 0000000..b21d2c5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/CentralAuthenticationServiceImplWithMokitoTests.java @@ -0,0 +1,84 @@ +/* + * 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. + */ + +package org.jasig.cas; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.jasig.cas.services.UnauthorizedProxyingException; +import org.jasig.cas.ticket.*; +import org.jasig.cas.ticket.registry.TicketRegistry; + + +import java.util.List; + +import org.junit.Test; +import static org.mockito.Mockito.*; +import static org.junit.Assert.*; + +/** + * Unit tests with the help of Mockito framework + * + * @author Dmitriy Kopylenko + */ +public class CentralAuthenticationServiceImplWithMokitoTests { + + @Test + public void disallowVendingServiceTicketsWhenServiceIsNotAllowedToProxy_CAS1019() throws TicketException { + //Main class under test + CentralAuthenticationServiceImpl cas = new CentralAuthenticationServiceImpl(); + + //Mock ST + ServiceTicket stMock = mock(ServiceTicket.class); + when(stMock.getId()).thenReturn("st-id"); + + //Mock TGT + TicketGrantingTicket tgtMock = mock(TicketGrantingTicket.class); + when(tgtMock.isExpired()).thenReturn(false); + when(tgtMock.grantServiceTicket(anyString(), any(Service.class), any(ExpirationPolicy.class), anyBoolean())).thenReturn(stMock); + List authnListMock = mock(List.class); + when(authnListMock.size()).thenReturn(2); // <-- criteria for testing the CAS-1019 feature + when(tgtMock.getChainedAuthentications()).thenReturn(authnListMock); + + //Mock TicketRegistry + TicketRegistry ticketRegMock = mock(TicketRegistry.class); + when(ticketRegMock.getTicket(anyString(), eq(TicketGrantingTicket.class))).thenReturn(tgtMock); + + //Mock ServicesManager + RegisteredServiceImpl registeredService = new RegisteredServiceImpl(); + registeredService.setAllowedToProxy(false); // <-- criteria for testing the CAS-1019 feature + ServicesManager smMock = mock(ServicesManager.class); + when(smMock.findServiceBy(any(Service.class))).thenReturn(registeredService); + + //Set the stubbed dependencies + cas.setTicketRegistry(ticketRegMock); + cas.setServicesManager(smMock); + + //Finally, test the feature + try{ + cas.grantServiceTicket("tgt-id", TestUtils.getService()); + fail("Should have thrown UnauthorizedProxyingException"); + } + catch (UnauthorizedProxyingException e) { + //Expected + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/TestDriver.java b/cas-server-core/src/test/java/org/jasig/cas/TestDriver.java new file mode 100644 index 0000000..7812436 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/TestDriver.java @@ -0,0 +1,17 @@ +package org.jasig.cas; + +import org.jasig.cas.authentication.handler.DefaultPasswordEncoder; +import org.junit.Test; + +/** + * @author Donghuang
+ * donghuang@wacai.com
+ * Aug 02, 2017 14:41 + */ +public class TestDriver { + + @Test + public void testPassword() { + System.err.println(new DefaultPasswordEncoder("MD5").encode("1234")); + } +} 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 0000000..c6f8edf --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/TestUtils.java @@ -0,0 +1,190 @@ +/* + * 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. + */ +package org.jasig.cas; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.ImmutableAuthentication; +import org.jasig.cas.authentication.principal.*; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ImmutableAssertionImpl; +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.util.ArrayList; +import java.util.List; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.2 + */ +public final class TestUtils { + + public static final String CONST_USERNAME = "test"; + + private static final String CONST_PASSWORD = "test1"; + + private static final String CONST_BAD_URL = "http://www.acs.rutgers.edu"; + + 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]; + + 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 TestUtils() { + // do not instanciate + } + + public static UsernamePasswordCredentials getCredentialsWithSameUsernameAndPassword() { + return getCredentialsWithSameUsernameAndPassword(CONST_USERNAME); + } + + public static UsernamePasswordCredentials getCredentialsWithSameUsernameAndPassword( + final String username) { + return getCredentialsWithDifferentUsernameAndPassword(username, + username); + } + + public static UsernamePasswordCredentials getCredentialsWithDifferentUsernameAndPassword() { + return getCredentialsWithDifferentUsernameAndPassword(CONST_USERNAME, + CONST_PASSWORD); + } + + public static UsernamePasswordCredentials getCredentialsWithDifferentUsernameAndPassword( + final String username, final String password) { + // noinspection LocalVariableOfConcreteClass + final UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials(); + usernamePasswordCredentials.setUsername(username); + usernamePasswordCredentials.setPassword(password); + + return usernamePasswordCredentials; + } + + public static HttpBasedServiceCredentials getHttpBasedServiceCredentials() { + return getHttpBasedServiceCredentials(CONST_GOOD_URL); + } + + public static HttpBasedServiceCredentials getBadHttpBasedServiceCredentials() { + return getHttpBasedServiceCredentials(CONST_BAD_URL); + } + + public static HttpBasedServiceCredentials getHttpBasedServiceCredentials( + final String url) { + try { + return new HttpBasedServiceCredentials(new URL(url)); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(); + } + } + + public static Principal getPrincipal() { + return getPrincipal(CONST_USERNAME); + } + + public static Principal getPrincipal(final String name) { + return new SimplePrincipal(name); + } + + public static Service getService() { + return getService(CONST_USERNAME); + } + + 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 new ImmutableAuthentication(getPrincipal()); + } + + public static Authentication getAuthenticationWithService() { + return new ImmutableAuthentication(getService()); + } + + public static Authentication getAuthentication(final String name) { + return new ImmutableAuthentication(getPrincipal(name)); + } + + 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 ImmutableAssertionImpl(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/authentication/AbstractAuthenticationTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/AbstractAuthenticationTests.java new file mode 100644 index 0000000..6a286bd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/AbstractAuthenticationTests.java @@ -0,0 +1,55 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.HashMap; +import java.util.Map; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public abstract class AbstractAuthenticationTests extends TestCase { + + protected Authentication authentication; + + protected Map attributes = new HashMap(); + + public final void testGetters() { + assertEquals("Principals are not equal", TestUtils.getPrincipal(), + this.authentication.getPrincipal()); + assertEquals("Authentication Attributes not equal.", + this.authentication.getAttributes(), this.attributes); + } + + public final void testNullHashMap() { + assertNotNull("Attributes are null.", TestUtils.getAuthentication() + .getAttributes()); + } + + public final void testEquals() { + assertTrue("Authentications are not equal", this.authentication + .equals(this.authentication)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/AuthenticationManagerImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/AuthenticationManagerImplTests.java new file mode 100644 index 0000000..b4eec6d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/AuthenticationManagerImplTests.java @@ -0,0 +1,111 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Arrays; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.AuthenticationHandler; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; +import org.jasig.cas.authentication.handler.UnsupportedCredentialsException; +import org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver; +import org.jasig.cas.util.HttpClient; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class AuthenticationManagerImplTests extends AbstractCentralAuthenticationServiceTest { + + @Test + public void testSuccessfulAuthentication() throws Exception { + assertEquals(TestUtils.getPrincipal(), + getAuthenticationManager().authenticate( + TestUtils.getCredentialsWithSameUsernameAndPassword()) + .getPrincipal()); + } + + @Test + public void testFailedAuthentication() throws Exception { + try { + getAuthenticationManager().authenticate( + TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + fail("Authentication should have failed."); + } catch (AuthenticationException e) { + return; + } + } + + @Test + public void testNoHandlerFound() throws AuthenticationException { + try { + getAuthenticationManager().authenticate(new Credentials(){ + + private static final long serialVersionUID = -4897240037527663222L; + // there is nothing to do here + }); + fail("Authentication should have failed."); + } catch (UnsupportedCredentialsException e) { + return; + } + } + + @Test(expected=UnsupportedCredentialsException.class) + public void testNoResolverFound() throws Exception { + AuthenticationManagerImpl manager = new AuthenticationManagerImpl(); + HttpBasedServiceCredentialsAuthenticationHandler authenticationHandler = new HttpBasedServiceCredentialsAuthenticationHandler(); + authenticationHandler.setHttpClient(new HttpClient()); + manager.setAuthenticationHandlers(Arrays.asList((AuthenticationHandler) authenticationHandler)); + manager.setCredentialsToPrincipalResolvers(Arrays.asList((CredentialsToPrincipalResolver) new UsernamePasswordCredentialsToPrincipalResolver())); + manager.authenticate(TestUtils.getHttpBasedServiceCredentials()); + } + + @Test(expected = BadCredentialsAuthenticationException.class) + public void testResolverReturnsNull() throws Exception { + AuthenticationManagerImpl manager = new AuthenticationManagerImpl(); + HttpBasedServiceCredentialsAuthenticationHandler authenticationHandler = new HttpBasedServiceCredentialsAuthenticationHandler(); + authenticationHandler.setHttpClient(new HttpClient()); + manager + .setAuthenticationHandlers(Arrays.asList((AuthenticationHandler) authenticationHandler)); + manager + .setCredentialsToPrincipalResolvers(Arrays.asList((CredentialsToPrincipalResolver) new TestCredentialsToPrincipalResolver())); + manager.authenticate(TestUtils.getHttpBasedServiceCredentials()); + } + + protected class TestCredentialsToPrincipalResolver implements CredentialsToPrincipalResolver { + + public Principal resolvePrincipal(Credentials credentials) { + return null; + } + + public boolean supports(final Credentials credentials) { + return true; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/DirectMappingAuthenticationManagerImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/DirectMappingAuthenticationManagerImplTests.java new file mode 100644 index 0000000..8e70bf7 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/DirectMappingAuthenticationManagerImplTests.java @@ -0,0 +1,92 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jasig.cas.authentication.DirectMappingAuthenticationManagerImpl.DirectAuthenticationHandlerMappingHolder; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.HttpBasedServiceCredentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver; + +import junit.framework.TestCase; + + +public class DirectMappingAuthenticationManagerImplTests extends TestCase { + + private DirectMappingAuthenticationManagerImpl manager = new DirectMappingAuthenticationManagerImpl(); + + protected void setUp() throws Exception { + this.manager = new DirectMappingAuthenticationManagerImpl(); + + final Map, DirectAuthenticationHandlerMappingHolder> mappings = new HashMap, DirectAuthenticationHandlerMappingHolder>(); + final List populators = new ArrayList(); + populators.add(new SamlAuthenticationMetaDataPopulator()); + + this.manager.setAuthenticationMetaDataPopulators(populators); + + final DirectAuthenticationHandlerMappingHolder d = new DirectAuthenticationHandlerMappingHolder(); + d.setAuthenticationHandler(new SimpleTestUsernamePasswordAuthenticationHandler()); + d.setCredentialsToPrincipalResolver(new UsernamePasswordCredentialsToPrincipalResolver()); + + mappings.put(UsernamePasswordCredentials.class, d); + + this.manager.setCredentialsMapping(mappings); + super.setUp(); + } + + public void testAuthenticateUsernamePassword() throws Exception { + final UsernamePasswordCredentials c = new UsernamePasswordCredentials(); + c.setUsername("Test"); + c.setPassword("Test"); + final Authentication authentication = this.manager.authenticate(c); + + assertEquals(c.getUsername(), authentication.getPrincipal().getId()); + } + + public void testAuthenticateBadUsernamePassword() throws Exception { + final UsernamePasswordCredentials c = new UsernamePasswordCredentials(); + c.setUsername("Test"); + c.setPassword("Test2"); + try { + this.manager.authenticate(c); + fail(); + } catch (final BadCredentialsAuthenticationException e) { + return; + } + } + + public void testAuthenticateHttp() throws Exception { + + try { + final HttpBasedServiceCredentials c = new HttpBasedServiceCredentials(new URL("http://www.cnn.com")); + this.manager.authenticate(c); + fail("Exception expected."); + } catch (final IllegalArgumentException e) { + return; + } + } +} 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 0000000..953503d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/ImmutableAuthenticationTests.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Date; + +import org.jasig.cas.TestUtils; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class ImmutableAuthenticationTests extends AbstractAuthenticationTests { + + protected void setUp() throws Exception { + super.setUp(); + this.authentication = new ImmutableAuthentication(TestUtils + .getPrincipal(), this.attributes); + } + + public void testAuthenticatedDate() { + Date dateFromFirstCall = this.authentication.getAuthenticatedDate(); + Date dateFromSecondCall = this.authentication.getAuthenticatedDate(); + + assertNotSame("Dates are the same.", dateFromFirstCall, + dateFromSecondCall); + assertEquals("Dates are not equal.", dateFromFirstCall, + dateFromSecondCall); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/MutableAuthenticationTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/MutableAuthenticationTests.java new file mode 100644 index 0000000..9acb220 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/MutableAuthenticationTests.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.Date; + +import org.jasig.cas.TestUtils; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class MutableAuthenticationTests extends AbstractAuthenticationTests { + + protected void setUp() throws Exception { + super.setUp(); + this.authentication = new MutableAuthentication(TestUtils + .getPrincipal()); + this.attributes = this.authentication.getAttributes(); + } + + public void testAuthenticatedDate() { + Date dateFromFirstCall = this.authentication.getAuthenticatedDate(); + Date dateFromSecondCall = this.authentication.getAuthenticatedDate(); + + assertSame("Dates are the same.", dateFromFirstCall, dateFromSecondCall); + assertEquals("Dates are not equal.", dateFromFirstCall, + dateFromSecondCall); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/SamlAuthenticationMetaDataPopulatorTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/SamlAuthenticationMetaDataPopulatorTests.java new file mode 100644 index 0000000..2654862 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/SamlAuthenticationMetaDataPopulatorTests.java @@ -0,0 +1,86 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class SamlAuthenticationMetaDataPopulatorTests extends TestCase { + + private SamlAuthenticationMetaDataPopulator populator; + + protected void setUp() throws Exception { + this.populator = new SamlAuthenticationMetaDataPopulator(); + super.setUp(); + } + + public void testAuthenticationTypeFound() { + final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(); + final MutableAuthentication ma = new MutableAuthentication(TestUtils.getPrincipal()); + + final Authentication m2 = this.populator.populateAttributes(ma, credentials); + + assertEquals(m2.getAttributes().get("samlAuthenticationStatementAuthMethod"), SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_PASSWORD); + } + + public void testAuthenticationTypeNotFound() { + final CustomCredentials credentials = new CustomCredentials(); + final MutableAuthentication ma = new MutableAuthentication(TestUtils.getPrincipal()); + + final Authentication m2 = this.populator.populateAttributes(ma, credentials); + + assertNull(m2.getAttributes().get("samlAuthenticationStatementAuthMethod")); + } + + public void testAuthenticationTypeFoundCustom() { + final CustomCredentials credentials = new CustomCredentials(); + + final Map added = new HashMap(); + added.put(CustomCredentials.class.getName(), "FF"); + + this.populator.setUserDefinedMappings(added); + + final MutableAuthentication ma = new MutableAuthentication(TestUtils.getPrincipal()); + + final Authentication m2 = this.populator.populateAttributes(ma, credentials); + + assertEquals("FF", m2.getAttributes().get("samlAuthenticationStatementAuthMethod")); + } + + protected class CustomCredentials implements Credentials { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = -3387599342233073148L; + // nothing to do + } +} 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 0000000..79fc735 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/SimpleServiceTests.java @@ -0,0 +1,51 @@ +/* + * 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. + */ +package org.jasig.cas.authentication; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class SimpleServiceTests extends TestCase { + + public void testProperId() { + assertEquals("Ids are not equal.", TestUtils.CONST_USERNAME, TestUtils + .getService().getId()); + } + + public void testEqualsWithNull() { + assertFalse("Service matches null.", TestUtils.getService() + .equals(null)); + } + + public void testEqualsWithBadClass() { + assertFalse("Services matches String class.", TestUtils.getService() + .equals(new Object())); + } + + public void testEquals() { + assertTrue("Services are not equal.", TestUtils.getService().equals( + TestUtils.getService())); + } +} 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 0000000..efce471 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadCredentialsAuthenticationExceptionTests.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class BadCredentialsAuthenticationExceptionTests extends TestCase { + + private static final String CODE = "error.authentication.credentials.bad"; + + public void testGetCode() { + AuthenticationException e = new BadCredentialsAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + public void testThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BadCredentialsAuthenticationException e = new BadCredentialsAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final BadCredentialsAuthenticationException e = new BadCredentialsAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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 0000000..1e01cb8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadPasswordAuthenticationExceptionTests.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision: 39552 $ $Date: 2007-01-22 15:35:37 -0500 (Mon, 22 Jan 2007) $ + * @since 3.0 + */ +public final class BadPasswordAuthenticationExceptionTests extends TestCase { + + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.password"; + + public void testGetCode() { + AuthenticationException e = new BadPasswordAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + public void testThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BadPasswordAuthenticationException e = new BadPasswordAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final BadPasswordAuthenticationException e = new BadPasswordAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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 0000000..7ef5dd2 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BadUsernameOrPasswordAuthenticationExceptionTests.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision: 39552 $ $Date: 2007-01-22 15:35:37 -0500 (Mon, 22 Jan 2007) $ + * @since 3.1 + */ +public final class BadUsernameOrPasswordAuthenticationExceptionTests extends TestCase { + + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword"; + + public void testGetCode() { + AuthenticationException e = new BadUsernameOrPasswordAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + public void testThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BadUsernameOrPasswordAuthenticationException e = new BadUsernameOrPasswordAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final BadUsernameOrPasswordAuthenticationException e = new BadUsernameOrPasswordAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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 0000000..2b804fd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/BlockedCredentialsAuthenticationExceptionTests.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision: 39552 $ $Date: 2007-01-22 15:35:37 -0500 (Mon, 22 Jan 2007) $ + * @since 3.1 + */ +public final class BlockedCredentialsAuthenticationExceptionTests extends TestCase { + + private static final String CODE = "error.authentication.credentials.blocked"; + + public void testGetCode() { + AuthenticationException e = new BlockedCredentialsAuthenticationException (); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + public void testThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final BlockedCredentialsAuthenticationException e = new BlockedCredentialsAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final BlockedCredentialsAuthenticationException e = new BlockedCredentialsAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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/DefaultPasswordEncoderTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoderTests.java new file mode 100644 index 0000000..7081580 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/DefaultPasswordEncoderTests.java @@ -0,0 +1,68 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class DefaultPasswordEncoderTests extends TestCase { + + private final PasswordEncoder passwordEncoder = new DefaultPasswordEncoder("MD5"); + + public void testNullPassword() { + assertEquals(null, this.passwordEncoder.encode(null)); + } + + public void testMd5Hash() { + assertEquals("1f3870be274f6c49b3e31a0c6728957f", this.passwordEncoder + .encode("apple")); + } + + public void testSha1Hash() { + final PasswordEncoder pe = new DefaultPasswordEncoder("SHA1"); + + final String hash = pe.encode("this is a test"); + + assertEquals("fa26be19de6bff93f70bc2308434e4a440bbad02", hash); + + } + + public void testSha1Hash2() { + final PasswordEncoder pe = new DefaultPasswordEncoder("SHA1"); + + final String hash = pe.encode("TEST of the SYSTEM"); + + assertEquals("82ae28dfad565dd9882b94498a271caa29025d5f", hash); + + } + + public void testInvalidEncodingType() { + 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 0000000..0e4929f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/PlainTextPasswordEncoderTests.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import org.jasig.cas.authentication.handler.PasswordEncoder; +import org.jasig.cas.authentication.handler.PlainTextPasswordEncoder; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class PlainTextPasswordEncoderTests extends TestCase { + + private final PasswordEncoder passwordEncoder = new PlainTextPasswordEncoder(); + + private final static String CONST_TO_ENCODE = "CAS IS COOL"; + + public void testNullValueToTranslate() { + assertEquals(null, this.passwordEncoder.encode(null)); + } + + public void testValueToTranslate() { + 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 0000000..fb3e9c8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnknownUsernameAuthenticationExceptionTests.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision: 39552 $ $Date: 2007-01-22 15:35:37 -0500 (Mon, 22 Jan 2007) $ + * @since 3.0 + */ +public final class UnknownUsernameAuthenticationExceptionTests extends TestCase { + + private static final String CODE = "error.authentication.credentials.bad.usernameorpassword.username"; + + public void testGetCode() { + AuthenticationException e = new UnknownUsernameAuthenticationException(); + assertEquals(CODE, e.getCode()); + assertEquals(CODE, e.toString()); + } + + public void testThrowableConstructor() { + final RuntimeException r = new RuntimeException(); + final UnknownUsernameAuthenticationException e = new UnknownUsernameAuthenticationException(r); + + assertEquals(CODE, e.getCode()); + assertEquals(r, e.getCause()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final UnknownUsernameAuthenticationException e = new UnknownUsernameAuthenticationException(MESSAGE); + + assertEquals(MESSAGE, e.getCode()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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 0000000..994701e --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/UnsupportedCredentialsExceptionTests.java @@ -0,0 +1,50 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class UnsupportedCredentialsExceptionTests extends TestCase { + + private static final String CODE = "error.authentication.credentials.unsupported"; + + public void testNoParamConstructor() { + new UnsupportedCredentialsException(); + } + + public void testGetCode() { + assertEquals(CODE, + new UnsupportedCredentialsException().getCode()); + } + + public void testThrowableConstructor() { + 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 0000000..12972c4 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/HttpBasedServiceCredentialsAuthenticationHandlerTests.java @@ -0,0 +1,85 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.util.HttpClient; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class HttpBasedServiceCredentialsAuthenticationHandlerTests extends + TestCase { + + private HttpBasedServiceCredentialsAuthenticationHandler authenticationHandler; + + protected void setUp() throws Exception { + this.authenticationHandler = new HttpBasedServiceCredentialsAuthenticationHandler(); + this.authenticationHandler.setHttpClient(new HttpClient()); + } + + public void testSupportsProperUserCredentials() { + assertTrue(this.authenticationHandler.supports(TestUtils + .getHttpBasedServiceCredentials())); + } + + public void testDoesntSupportBadUserCredentials() { + assertFalse(this.authenticationHandler.supports(TestUtils + .getCredentialsWithSameUsernameAndPassword())); + } + + public void testAcceptsProperCertificateCredentials() { + assertTrue(this.authenticationHandler.authenticate(TestUtils + .getHttpBasedServiceCredentials())); + } + + public void testRejectsInProperCertificateCredentials() { + assertFalse(this.authenticationHandler.authenticate(TestUtils + .getHttpBasedServiceCredentials("https://clearinghouse.ja-sig.org"))); + } + + public void testRejectsNonHttpsCredentials() { + assertFalse(this.authenticationHandler.authenticate(TestUtils + .getHttpBasedServiceCredentials("http://www.jasig.org"))); + } + + public void testAcceptsNonHttpsCredentials() { + this.authenticationHandler.setHttpClient(new HttpClient()); + this.authenticationHandler.setRequireSecure(false); + assertTrue(this.authenticationHandler.authenticate(TestUtils + .getHttpBasedServiceCredentials("http://www.jasig.org"))); + } + + public void testNoAcceptableStatusCode() throws Exception { + assertFalse(this.authenticationHandler.authenticate(TestUtils + .getHttpBasedServiceCredentials("https://clue.acs.rutgers.edu"))); + } + + public void testNoAcceptableStatusCodeButOneSet() throws Exception { + final HttpClient httpClient = new HttpClient(); + httpClient.setAcceptableCodes(new int[] {900}); + this.authenticationHandler.setHttpClient(httpClient); + assertFalse(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 0000000..e92f091 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/JaasAuthenticationHandlerTests.java @@ -0,0 +1,68 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JaasAuthenticationHandlerTests extends TestCase { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private JaasAuthenticationHandler handler; + + protected void setUp() throws Exception { + String pathPrefix = System.getProperty("user.dir"); + pathPrefix = !pathPrefix.contains("cas-server-core") ? pathPrefix + + "/cas-server-core" : pathPrefix; + log.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(); + } + + public void testWithAlternativeRealm() throws Exception { + + this.handler.setRealm("TEST"); + assertFalse(this.handler.authenticate(TestUtils + .getCredentialsWithDifferentUsernameAndPassword("test", "test1"))); + } + + public void testWithAlternativeRealmAndValidCredentials() throws Exception { + this.handler.setRealm("TEST"); + assertTrue(this.handler.authenticate(TestUtils + .getCredentialsWithDifferentUsernameAndPassword("test", "test"))); + } + + public void testWithValidCredenials() throws Exception { + assertTrue(this.handler.authenticate(TestUtils + .getCredentialsWithSameUsernameAndPassword())); + } + + public void testWithInvalidCredentials() throws Exception { + assertFalse(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 0000000..20ee9e5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/MockLoginModule.java @@ -0,0 +1,68 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import java.util.Map; + +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; + +public class MockLoginModule implements LoginModule { + private CallbackHandler callbackHandler; + + public void initialize(Subject subject, CallbackHandler handler, Map arg2, + 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 (Exception e) { + throw new LoginException(); + } + + final String userName = ((NameCallback) callbacks[0]).getName(); + final String password = new String(((PasswordCallback) callbacks[1]).getPassword()); + + if (userName.equals("test") && password.equals("test")) { + 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/SimpleTestUsernamePasswordHandlerTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordHandlerTests.java new file mode 100644 index 0000000..276715e --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/handler/support/SimpleTestUsernamePasswordHandlerTests.java @@ -0,0 +1,101 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.handler.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.PlainTextPasswordEncoder; +import org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; + +import junit.framework.TestCase; + +/** + * Test of the simple username/password handler + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class SimpleTestUsernamePasswordHandlerTests extends TestCase { + + private SimpleTestUsernamePasswordAuthenticationHandler authenticationHandler; + + protected void setUp() throws Exception { + this.authenticationHandler = new SimpleTestUsernamePasswordAuthenticationHandler(); + this.authenticationHandler + .setPasswordEncoder(new PlainTextPasswordEncoder()); + } + + public void testSupportsProperUserCredentials() { + assertTrue(this.authenticationHandler.supports(TestUtils + .getCredentialsWithSameUsernameAndPassword())); + } + + public void testDoesntSupportBadUserCredentials() { + assertFalse(this.authenticationHandler.supports(TestUtils + .getHttpBasedServiceCredentials())); + } + + public void testValidUsernamePassword() throws AuthenticationException { + assertTrue(this.authenticationHandler.authenticate(TestUtils + .getCredentialsWithSameUsernameAndPassword())); + } + + public void testInvalidUsernamePassword() { + try { + assertFalse(this.authenticationHandler.authenticate(TestUtils + .getCredentialsWithDifferentUsernameAndPassword())); + } catch (AuthenticationException ae) { + // this is okay + } + } + + public void testNullUsernamePassword() { + try { + assertFalse(this.authenticationHandler.authenticate(TestUtils + .getCredentialsWithSameUsernameAndPassword(null))); + } catch (AuthenticationException ae) { + // this is okay + } + } + + public void testAlternateClass() { + this.authenticationHandler.setClassToSupport(UsernamePasswordCredentials.class); + assertTrue(this.authenticationHandler.supports(new UsernamePasswordCredentials())); + } + + public void testAlternateClassWithSubclassSupport() { + this.authenticationHandler.setClassToSupport(UsernamePasswordCredentials.class); + this.authenticationHandler.setSupportSubClasses(true); + assertTrue(this.authenticationHandler.supports(new ExtendedCredentials())); + } + + public void testAlternateClassWithNoSubclassSupport() { + this.authenticationHandler.setClassToSupport(UsernamePasswordCredentials.class); + this.authenticationHandler.setSupportSubClasses(false); + assertFalse(this.authenticationHandler.supports(new ExtendedCredentials())); + } + + protected class ExtendedCredentials extends UsernamePasswordCredentials { + + private static final long serialVersionUID = 406992293105518363L; + // nothing to see here + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/GoogleAccountsServiceTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/GoogleAccountsServiceTests.java new file mode 100644 index 0000000..ed9ccc1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/GoogleAccountsServiceTests.java @@ -0,0 +1,100 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import junit.framework.TestCase; +import org.apache.commons.codec.binary.Base64; +import org.jasig.cas.TestUtils; +import org.jasig.cas.util.PrivateKeyFactoryBean; +import org.jasig.cas.util.PublicKeyFactoryBean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; +import java.util.zip.DeflaterOutputStream; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class GoogleAccountsServiceTests extends TestCase { + + 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("SAMLRequest", encodeMessage(SAMLRequest)); + + return GoogleAccountsService.createServiceFrom(request, privateKey, publicKey, "username"); + } + + protected void setUp() throws Exception { + this.googleAccountsService = getGoogleAccountsService(); + this.googleAccountsService.setPrincipal(TestUtils.getPrincipal()); + } + + + // XXX: re-enable when we figure out JVM requirements + public void testResponse() { + return; + // final Response response = this.googleAccountsService.getResponse("ticketId"); + // assertEquals(ResponseType.POST, response.getResponseType()); + // assertTrue(response.getAttributes().containsKey("SAMLResponse")); + } + + + protected static String encodeMessage(final String xmlString) throws IOException { + byte[] xmlBytes = xmlString.getBytes("UTF-8"); + ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream( + byteOutputStream); + deflaterOutputStream.write(xmlBytes, 0, xmlBytes.length); + deflaterOutputStream.close(); + + // next, base64 encode it + Base64 base64Encoder = new Base64(); + byte[] base64EncodedByteArray = base64Encoder.encode(byteOutputStream + .toByteArray()); + return new String(base64EncodedByteArray); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsTests.java new file mode 100644 index 0000000..c953acf --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsTests.java @@ -0,0 +1,60 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import java.net.URL; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class HttpBasedServiceCredentialsTests extends TestCase { + + public void testProperUrl() { + assertEquals(TestUtils.CONST_GOOD_URL, TestUtils + .getHttpBasedServiceCredentials().getCallbackUrl().toExternalForm()); + } + + public void testEqualsWithNull() throws Exception { + final HttpBasedServiceCredentials c = new HttpBasedServiceCredentials(new URL("http://www.cnn.com")); + + assertFalse(c.equals(null)); + } + + public void testEqualsWithFalse() throws Exception { + final HttpBasedServiceCredentials c = new HttpBasedServiceCredentials(new URL("http://www.cnn.com")); + final HttpBasedServiceCredentials c2 = new HttpBasedServiceCredentials(new URL("http://www.msn.com")); + + assertFalse(c.equals(c2)); + assertFalse(c.equals(new Object())); + } + + public void testEqualsWithTrue() throws Exception { + final HttpBasedServiceCredentials c = new HttpBasedServiceCredentials(new URL("http://www.cnn.com")); + final HttpBasedServiceCredentials c2 = new HttpBasedServiceCredentials(new URL("http://www.cnn.com")); + + assertTrue(c.equals(c2)); + assertTrue(c2.equals(c)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsToPrincipalResolverTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsToPrincipalResolverTests.java new file mode 100644 index 0000000..e8aa4e8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/HttpBasedServiceCredentialsToPrincipalResolverTests.java @@ -0,0 +1,55 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class HttpBasedServiceCredentialsToPrincipalResolverTests extends + TestCase { + + private CredentialsToPrincipalResolver resolver = new HttpBasedServiceCredentialsToPrincipalResolver(); + + public void testInValidSupportsCredentials() { + assertFalse(this.resolver.supports(TestUtils + .getCredentialsWithSameUsernameAndPassword())); + } + + public void testNullSupportsCredentials() { + assertFalse(this.resolver.supports(null)); + } + + public void testValidSupportsCredentials() { + assertTrue(this.resolver.supports(TestUtils + .getHttpBasedServiceCredentials())); + } + + public void testValidCredentials() { + assertEquals(this.resolver.resolvePrincipal( + TestUtils.getHttpBasedServiceCredentials()).getId(), TestUtils + .getHttpBasedServiceCredentials().getCallbackUrl().toExternalForm()); + } +} 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 0000000..d4fa52b --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeAuthenticationMetaDataPopulatorTests.java @@ -0,0 +1,67 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.MutableAuthentication; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public class RememberMeAuthenticationMetaDataPopulatorTests extends TestCase { + + private RememberMeAuthenticationMetaDataPopulator p = new RememberMeAuthenticationMetaDataPopulator(); + + public void testWithTrueRememberMeCredentials() { + final Authentication auth = new MutableAuthentication(TestUtils.getPrincipal()); + final RememberMeUsernamePasswordCredentials c = new RememberMeUsernamePasswordCredentials(); + c.setRememberMe(true); + + final Authentication auth2 = this.p.populateAttributes(auth, c); + + assertEquals(Boolean.TRUE, auth2.getAttributes().get(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME)); + } + + public void testWithFalseRememberMeCredentials() { + final Authentication auth = new MutableAuthentication(TestUtils.getPrincipal()); + final RememberMeUsernamePasswordCredentials c = new RememberMeUsernamePasswordCredentials(); + c.setRememberMe(false); + + final Authentication auth2 = this.p.populateAttributes(auth, c); + + assertNull(auth2.getAttributes().get(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME)); + } + + + public void testWithoutRememberMeCredentials() { + final Authentication auth = new MutableAuthentication(TestUtils.getPrincipal()); + final Authentication auth2 = this.p.populateAttributes(auth, TestUtils.getCredentialsWithSameUsernameAndPassword()); + + assertNull(auth2.getAttributes().get(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME)); + } + + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeUsernamePasswordCredentialsTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeUsernamePasswordCredentialsTests.java new file mode 100644 index 0000000..29dbbba --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/RememberMeUsernamePasswordCredentialsTests.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import junit.framework.TestCase; + +/** + * Tests for RememberMeUsernamePasswordCredentials + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public final class RememberMeUsernamePasswordCredentialsTests extends TestCase { + + public void testGettersAndSetters() { + final RememberMeUsernamePasswordCredentials c = new RememberMeUsernamePasswordCredentials(); + 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/principal/ResponseTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ResponseTests.java new file mode 100644 index 0000000..5490767 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ResponseTests.java @@ -0,0 +1,82 @@ +/* + * 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. + */ + +package org.jasig.cas.authentication.principal; + +import junit.framework.TestCase; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.4.4 + */ +public class ResponseTests extends TestCase { + + public void testConstructionWithoutFragmentAndNoQueryString() { + final String url = "http://localhost:8080/foo"; + final Map attributes = new HashMap(); + attributes.put("ticket", "foobar"); + final Response response = Response.getRedirectResponse(url, attributes); + assertEquals(url + "?ticket=foobar", response.getUrl()); + } + + public void testConstructionWithoutFragmentButHasQueryString() { + final String url = "http://localhost:8080/foo?test=boo"; + final Map attributes = new HashMap(); + attributes.put("ticket", "foobar"); + final Response response = Response.getRedirectResponse(url, attributes); + assertEquals(url + "&ticket=foobar", response.getUrl()); + } + + public void testConstructionWithFragmentAndQueryString() { + final String url = "http://localhost:8080/foo?test=boo#hello"; + final Map attributes = new HashMap(); + attributes.put("ticket", "foobar"); + final Response response = Response.getRedirectResponse(url, attributes); + assertEquals("http://localhost:8080/foo?test=boo&ticket=foobar#hello", response.getUrl()); + } + + public void testConstructionWithFragmentAndNoQueryString() { + final String url = "http://localhost:8080/foo#hello"; + final Map attributes = new HashMap(); + attributes.put("ticket", "foobar"); + final Response response = Response.getRedirectResponse(url, attributes); + assertEquals("http://localhost:8080/foo?ticket=foobar#hello", response.getUrl()); + + } + + public void testUrlSanitization() { + 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 = Response.getRedirectResponse(url, attributes); + assertEquals("https://www.example.com Location: javascript: ?ticket=ST-12345", response.getUrl()); + } + + public void testUrlWithUnicode() { + final String url = "https://www.example.com/πολιτικῶν"; + final Map attributes = new HashMap(); + attributes.put("ticket", "ST-12345"); + final Response response = Response.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/SamlServiceTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SamlServiceTests.java new file mode 100644 index 0000000..fc963a9 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SamlServiceTests.java @@ -0,0 +1,74 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import junit.framework.TestCase; + +import org.jasig.cas.authentication.principal.Response.ResponseType; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class SamlServiceTests extends TestCase { + + public void testResponse() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "service"); + final SamlService impl = SamlService.createServiceFrom(request, null); + + final Response response = impl.getResponse("ticketId"); + assertNotNull(response); + assertEquals(ResponseType.REDIRECT, response.getResponseType()); + assertTrue(response.getUrl().contains("SAMLart=")); + } + + public void testResponseForJsession() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "http://www.cnn.com/;jsession=test"); + final SamlService impl = SamlService.createServiceFrom(request, null); + + assertEquals("http://www.cnn.com/", impl.getId()); + } + + public void testResponseWithNoTicket() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "service"); + final SamlService impl = SamlService.createServiceFrom(request, null); + + final Response response = impl.getResponse(null); + assertNotNull(response); + assertEquals(ResponseType.REDIRECT, response.getResponseType()); + assertFalse(response.getUrl().contains("SAMLart=")); + } + + public void testRequestBody() { + final String body = "artifact"; + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setContent(body.getBytes()); + + final SamlService impl = SamlService.createServiceFrom(request, null); + assertEquals("artifact", impl.getArtifactId()); + assertEquals("_192.168.16.51.1024506224022", impl.getRequestID()); + } +} 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 0000000..d05207f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/ShibbolethCompatiblePersistentIdGeneratorTests.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public class ShibbolethCompatiblePersistentIdGeneratorTests extends TestCase { + + public void testGenerator() { + final ShibbolethCompatiblePersistentIdGenerator generator = new ShibbolethCompatiblePersistentIdGenerator(); + generator.setSalt("scottssalt"); + + final Principal p = TestUtils.getPrincipal(); + final Service s = TestUtils.getService(); + + final String value = generator.generate(p, s); + + assertNotNull(value); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimplePrincipalTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimplePrincipalTests.java new file mode 100644 index 0000000..03bf49b --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimplePrincipalTests.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class SimplePrincipalTests extends TestCase { + + public void testProperId() { + final String id = "test"; + assertEquals(id, new SimplePrincipal(id).getId()); + } + + public void testEqualsWithNull() { + assertFalse(new SimplePrincipal("test").equals(null)); + } + + public void testEqualsWithBadClass() { + assertFalse(new SimplePrincipal("test").equals("test")); + } + + public void testEquals() { + assertTrue(new SimplePrincipal("test").equals(new SimplePrincipal( + "test"))); + } +} 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 0000000..e2329cd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/SimpleWebApplicationServiceImplTests.java @@ -0,0 +1,87 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import junit.framework.TestCase; + +import org.jasig.cas.authentication.principal.Response.ResponseType; +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * + * @author Scott Battaglia + * @author Arnaud Lesueur + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class SimpleWebApplicationServiceImplTests extends TestCase { + + public void testResponse() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "service"); + final SimpleWebApplicationServiceImpl impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + final Response response = impl.getResponse("ticketId"); + assertNotNull(response); + assertEquals(ResponseType.REDIRECT, response.getResponseType()); + } + + public void testResponseForJsession() { + 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()); + } + + public void testResponseWithNoTicket() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "service"); + final WebApplicationService impl = SimpleWebApplicationServiceImpl.createServiceFrom(request); + + final Response response = impl.getResponse(null); + assertNotNull(response); + assertEquals(ResponseType.REDIRECT, response.getResponseType()); + assertFalse(response.getUrl().contains("ticket=")); + } + + public void testResponseWithNoTicketAndNoParameterInServiceURL() { + 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(ResponseType.REDIRECT, response.getResponseType()); + assertFalse(response.getUrl().contains("ticket=")); + assertEquals("http://foo.com/",response.getUrl()); + } + + public void testResponseWithNoTicketAndOneParameterInServiceURL() { + 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(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/principal/UsernamePasswordCredentialsTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsTests.java new file mode 100644 index 0000000..3620bd0 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsTests.java @@ -0,0 +1,55 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class UsernamePasswordCredentialsTests extends TestCase { + + public void testSetGetUsername() { + final UsernamePasswordCredentials c = new UsernamePasswordCredentials(); + final String userName = "test"; + + c.setUsername(userName); + + assertEquals(userName, c.getUsername()); + } + + public void testSetGetPassword() { + final UsernamePasswordCredentials c = new UsernamePasswordCredentials(); + final String password = "test"; + + c.setPassword(password); + + assertEquals(password, c.getPassword()); + } + + public void testEquals() { + assertFalse(TestUtils.getCredentialsWithDifferentUsernameAndPassword().equals(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/principal/UsernamePasswordCredentialsToPrincipalResolverTests.java b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsToPrincipalResolverTests.java new file mode 100644 index 0000000..37851ef --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/authentication/principal/UsernamePasswordCredentialsToPrincipalResolverTests.java @@ -0,0 +1,56 @@ +/* + * 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. + */ +package org.jasig.cas.authentication.principal; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public final class UsernamePasswordCredentialsToPrincipalResolverTests extends + TestCase { + + private CredentialsToPrincipalResolver resolver = new UsernamePasswordCredentialsToPrincipalResolver(); + + public void testValidSupportsCredentials() { + assertTrue(this.resolver.supports(TestUtils + .getCredentialsWithSameUsernameAndPassword())); + } + + public void testNullSupportsCredentials() { + assertFalse(this.resolver.supports(null)); + } + + public void testInvalidSupportsCredentials() { + assertFalse(this.resolver.supports(TestUtils + .getHttpBasedServiceCredentials())); + } + + public void testValidCredentials() { + Principal p = this.resolver.resolvePrincipal(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + + assertEquals(p.getId(), TestUtils + .getCredentialsWithSameUsernameAndPassword().getUsername()); + } +} 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 0000000..bbe37c5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockApplicationEvent.java @@ -0,0 +1,31 @@ +/* + * 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. + */ +package org.jasig.cas.mock; + +import org.springframework.context.ApplicationEvent; + +public class MockApplicationEvent extends ApplicationEvent { + + private static final long serialVersionUID = 3761968285092032567L; + + public MockApplicationEvent(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 0000000..e78a571 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockAuthenticationMetaDataPopulator.java @@ -0,0 +1,33 @@ +/* + * 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. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.AuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.principal.Credentials; + +public class MockAuthenticationMetaDataPopulator implements + AuthenticationMetaDataPopulator { + + public Authentication populateAttributes(Authentication authentication, + Credentials credentials) { + return authentication; + } + +} 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 0000000..d47d051 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockService.java @@ -0,0 +1,75 @@ +/* + * 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. + */ +package org.jasig.cas.mock; + +import java.util.Map; + +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Response; +import org.jasig.cas.authentication.principal.Service; + +/** + * Simple mock implementation of a service principal. + * + * @author Marvin S. Addison + * @version $Revision: $ + * + */ +public class MockService implements Service { + /** MockService.java */ + private static final long serialVersionUID = 117438127028057173L; + private boolean loggedOut = false; + private String id; + + public MockService(final String id) { + this.id = id; + } + + public String getArtifactId() { + return null; + } + + public Response getResponse(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 0000000..ace43f7 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockServiceTicket.java @@ -0,0 +1,93 @@ +/* + * 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. + */ +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 + * @version $Revision: $ + */ +public class MockServiceTicket implements ServiceTicket { + 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 0000000..18dc284 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockTicketGrantingTicket.java @@ -0,0 +1,110 @@ +/* + * 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. + */ +package org.jasig.cas.mock; + +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.ImmutableAuthentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.authentication.principal.SimplePrincipal; +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.List; + +/** + * Mock ticket-granting ticket; + * + * @author Marvin S. Addison + * @version $Revision: $ + */ +public class MockTicketGrantingTicket implements TicketGrantingTicket { + + public static final UniqueTicketIdGenerator ID_GENERATOR = new DefaultUniqueTicketIdGenerator(); + + private final String id; + + private final Authentication authentication; + + private final Date created; + + private int usageCount; + + private boolean expired; + + + public MockTicketGrantingTicket(final String principal) { + id = ID_GENERATOR.getNewTicketId("TGT"); + authentication = new ImmutableAuthentication(new SimplePrincipal(principal)); + created = new Date(); + } + + public Authentication getAuthentication() { + return authentication; + } + + public ServiceTicket grantServiceTicket(final Service service) { + return grantServiceTicket(ID_GENERATOR.getNewTicketId("ST"), service, null, true); + } + + public ServiceTicket grantServiceTicket( + final String id, + final Service service, + final ExpirationPolicy expirationPolicy, + final boolean credentialsProvided) { + usageCount++; + return new MockServiceTicket(id, service, this); + } + + public void expire() { + expired = true; + } + + public boolean isRoot() { + return true; + } + + public List getChainedAuthentications() { + return Collections.emptyList(); + } + + public String getId() { + return id; + } + + public boolean isExpired() { + return expired; + } + + public TicketGrantingTicket getGrantingTicket() { + return this; + } + + public long getCreationTime() { + return created.getTime(); + } + + public int getCountOfUses() { + return usageCount; + } +} 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 0000000..e536169 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/mock/MockValidationSpecification.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class MockValidationSpecification implements ValidationSpecification { + + private boolean test; + + public MockValidationSpecification(boolean test) { + this.test = test; + } + + public boolean isSatisfiedBy(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 0000000..8112d2c --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractCacheMonitorTests.java @@ -0,0 +1,77 @@ +/* + * 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. + */ + +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 testObserveOk() 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 testObserveWarn() 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 testObserveError() 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 testObserveError2() 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 0000000..074f3ee --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/AbstractPoolMonitorTests.java @@ -0,0 +1,109 @@ +/* + * 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. + */ +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 testObserveOK() 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 testObserveWarn() 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 testObserveError() 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/DataSourceMonitorTests.java b/cas-server-core/src/test/java/org/jasig/cas/monitor/DataSourceMonitorTests.java new file mode 100644 index 0000000..ba31744 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/DataSourceMonitorTests.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +import java.util.concurrent.Executors; +import javax.sql.DataSource; + +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 DataSourceMonitor}. + * + * @author Marvin S. Addison + * @since 3.5.1 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/jpaTestApplicationContext.xml") +public class DataSourceMonitorTests { + + @Autowired + private DataSource dataSource; + + @Test + public void testObserve() 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-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 0000000..ee51a44 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/HealthCheckMonitorTests.java @@ -0,0 +1,93 @@ +/* + * 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. + */ +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 testObserveUnknown() throws Exception { + assertEquals(StatusCode.UNKNOWN, this.monitor.observe().getCode()); + } + + @Test + public void testObserveOk() 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 testObserveWarn() 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 testThrowsUncheckedException() 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 0000000..fd55545 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/MemoryMonitorTests.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +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 testObserveOk() throws Exception { + assertEquals(StatusCode.OK, new MemoryMonitor().observe().getCode()); + } + + @Test + public void testObserveWarn() 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 0000000..f683a5f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/monitor/SessionMonitorTests.java @@ -0,0 +1,128 @@ +/* + * 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. + */ +package org.jasig.cas.monitor; + +import org.jasig.cas.authentication.ImmutableAuthentication; +import org.jasig.cas.authentication.principal.SimplePrincipal; +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.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; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for {@link SessionMonitor} class. + * + * @author Marvin S. Addison + * @since 3.5.0 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={"classpath:/jpaTestApplicationContext.xml"}) +@Transactional +public class SessionMonitorTests { + + private static final ExpirationPolicy TEST_EXP_POLICY = new HardTimeoutExpirationPolicy(10000); + private static final UniqueTicketIdGenerator GENERATOR = new DefaultUniqueTicketIdGenerator(); + + @Autowired + private JpaTicketRegistry jpaRegistry; + + 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 testObserveOk() 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 testObserveWarnSessionsExceeded() 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 testObserveWarnServiceTicketsExceeded() 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")); + } + + @Test + @Rollback(false) + public void testObserveOkJpaTicketRegistry() 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"), + new ImmutableAuthentication(new SimplePrincipal("grover")), + TEST_EXP_POLICY); + registry.addTicket(ticket); + } + String id; + 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 0000000..366fcee --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/remoting/server/RemoteCentralAuthenticationServiceTests.java @@ -0,0 +1,165 @@ +/* + * 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. + */ +package org.jasig.cas.remoting.server; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketException; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +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 testValidCredentials() throws TicketException { + this.remoteCentralAuthenticationService.createTicketGrantingTicket(TestUtils.getCredentialsWithSameUsernameAndPassword()); + } + + @Test + public void testInvalidCredentials() throws TicketException { + try { + this.remoteCentralAuthenticationService.createTicketGrantingTicket(TestUtils.getCredentialsWithDifferentUsernameAndPassword(null, null)); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException e) { + return; + } + } + + @Test + public void testDontUseValidatorsToCheckValidCredentials() { + try { + this.remoteCentralAuthenticationService.createTicketGrantingTicket(TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + fail("TicketException expected."); + } catch (TicketException e) { + return; + } + } + + @Test + public void testDestroyTicketGrantingTicket() { + this.remoteCentralAuthenticationService + .destroyTicketGrantingTicket("test"); + } + + @Test + public void testGrantServiceTicketWithValidTicketGrantingTicket() + throws TicketException { + final String ticketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + this.remoteCentralAuthenticationService.grantServiceTicket(ticketId, + TestUtils.getService()); + } + + @Test + public void testGrantServiceTicketWithValidCredentials() + throws TicketException { + final String ticketGrantingTicketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + this.remoteCentralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId, TestUtils.getService(), TestUtils + .getCredentialsWithSameUsernameAndPassword()); + } + + @Test + public void testGrantServiceTicketWithNullCredentials() + throws TicketException { + final String ticketGrantingTicketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + this.remoteCentralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId, TestUtils.getService(), null); + } + + @Test + public void testGrantServiceTicketWithEmptyCredentials() + throws TicketException { + final String ticketGrantingTicketId = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + try { + this.remoteCentralAuthenticationService.grantServiceTicket( + ticketGrantingTicketId, TestUtils.getService(), TestUtils + .getCredentialsWithDifferentUsernameAndPassword("", "")); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException e) { + return; + } + } + + @Test + public void testValidateServiceTicketWithValidService() + throws TicketException { + final String ticketGrantingTicket = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = this.remoteCentralAuthenticationService + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + + this.remoteCentralAuthenticationService.validateServiceTicket( + serviceTicket, TestUtils.getService()); + } + + @Test + public void testDelegateTicketGrantingTicketWithValidCredentials() + throws TicketException { + final String ticketGrantingTicket = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = this.remoteCentralAuthenticationService + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + this.remoteCentralAuthenticationService.delegateTicketGrantingTicket( + serviceTicket, TestUtils.getHttpBasedServiceCredentials()); + } + + @Test + public void testDelegateTicketGrantingTicketWithInvalidCredentials() + throws TicketException { + final String ticketGrantingTicket = this.remoteCentralAuthenticationService + .createTicketGrantingTicket(TestUtils + .getCredentialsWithSameUsernameAndPassword()); + final String serviceTicket = this.remoteCentralAuthenticationService + .grantServiceTicket(ticketGrantingTicket, TestUtils.getService()); + try { + this.remoteCentralAuthenticationService + .delegateTicketGrantingTicket(serviceTicket, TestUtils + .getCredentialsWithDifferentUsernameAndPassword("", "")); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException e) { + return; + } + + } +} 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 0000000..7ba9db3 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/AbstractRegisteredServiceTests.java @@ -0,0 +1,100 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Unit test for {@link AbstractRegisteredService}. + * + * @author Marvin S. Addison + * @version $Revision: $ + */ +public class AbstractRegisteredServiceTests { + + private AbstractRegisteredService r = new AbstractRegisteredService() { + public void setServiceId(final String id) { + serviceId = id; + } + + protected AbstractRegisteredService newInstance() { + return this; + } + + public boolean matches(final Service service) { + return true; + } + }; + + @Test + public void testSettersAndGetters() { + final long ID = 1000; + final String DESCRIPTION = "test"; + final String SERVICEID = "serviceId"; + final String THEME = "theme"; + final String NAME = "name"; + final boolean ENABLED = false; + final boolean ALLOWED_TO_PROXY = false; + final boolean ANONYMOUS_ACCESS = true; + final boolean SSO_ENABLED = false; + final List ALLOWED_ATTRIBUTES = Arrays.asList("Test"); + + this.r.setAllowedAttributes(ALLOWED_ATTRIBUTES); + this.r.setAllowedToProxy(ALLOWED_TO_PROXY); + this.r.setAnonymousAccess(ANONYMOUS_ACCESS); + this.r.setDescription(DESCRIPTION); + this.r.setEnabled(ENABLED); + this.r.setId(ID); + this.r.setName(NAME); + this.r.setServiceId(SERVICEID); + this.r.setSsoEnabled(SSO_ENABLED); + this.r.setTheme(THEME); + + assertEquals(ALLOWED_ATTRIBUTES, this.r.getAllowedAttributes()); + assertEquals(ALLOWED_TO_PROXY, this.r.isAllowedToProxy()); + assertEquals(ANONYMOUS_ACCESS, this.r.isAnonymousAccess()); + assertEquals(DESCRIPTION, this.r.getDescription()); + assertEquals(ENABLED, this.r.isEnabled()); + assertEquals(ID, this.r.getId()); + assertEquals(NAME, this.r.getName()); + assertEquals(SERVICEID, this.r.getServiceId()); + assertEquals(SSO_ENABLED, this.r.isSsoEnabled()); + assertEquals(THEME, this.r.getTheme()); + + assertFalse(this.r.equals(null)); + assertFalse(this.r.equals(new Object())); + assertTrue(this.r.equals(this.r)); + + this.r.setAllowedAttributes(null); + assertNotNull(this.r.getAllowedAttributes()); + } + + @Test + public void testEquals() throws Exception { + assertTrue(r.equals(r.clone())); + assertFalse(new RegisteredServiceImpl().equals(null)); + assertFalse(new RegisteredServiceImpl().equals(new Object())); + } +} 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 0000000..87338d8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/DefaultServicesManagerImplTests.java @@ -0,0 +1,200 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.Service; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * + * @author battags + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.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 testSaveAndGet() { + 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 testSaveWithReturnedPersistedInstance() { + 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 testDeleteAndGet() { + 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 testDeleteNotExistentService() { + assertNull(this.defaultServicesManagerImpl.delete(1500)); + } + + @Test + public void testMatchesExistingService() { + 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 testAllService() { + 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 testEvaluationOrderOfServices() { + 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()); + + } + + protected class SimpleService implements Service { + + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 6572142033945243669L; + private String id; + + protected SimpleService(final String id) { + this.id = id; + } + + public Map getAttributes() { + return null; + } + + public String getId() { + return this.id; + } + + public void setPrincipal(Principal principal) { + // nothing to do + } + + public boolean logOutOfService(String sessionIdentifier) { + return false; + } + + public boolean matches(Service service) { + return true; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/JpaServiceRegistryDaoImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/JpaServiceRegistryDaoImplTests.java new file mode 100644 index 0000000..f39b45e --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/JpaServiceRegistryDaoImplTests.java @@ -0,0 +1,134 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +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 static org.junit.Assert.*; + +/** + * + * @author battags + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +@ContextConfiguration(locations= {"classpath:jpaTestApplicationContext.xml"}) +public class JpaServiceRegistryDaoImplTests extends AbstractTransactionalJUnit4SpringContextTests { + + @Autowired(required=true) + private JpaServiceRegistryDaoImpl dao; + + @Test + public void testSaveMethodWithNonExistentServiceAndNoAttributes() { + 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 testSaveMethodWithNonExistentServiceAndAttributes() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setName("test"); + r.setServiceId("testId"); + r.setTheme("theme"); + r.setDescription("description"); + r.setAllowedAttributes(Arrays.asList("Test")); + + final RegisteredService r2 = this.dao.save(r); + final RegisteredService r3 = this.dao.findServiceById(r2.getId()); + + assertEquals(r, r2); + assertEquals(r2, r3); + + assertTrue(r.getAllowedAttributes().contains("Test")); + } + + @Test + public void testSaveMethodWithExistingServiceNoAttribute() { + 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()); + } + + @Test + public void testSaveMethodWithExistingServiceAddAttribute() { + 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"); + r.setAllowedAttributes(Arrays.asList("Test")); + + this.dao.save(r); + + final RegisteredService r3 = this.dao.findServiceById(r.getId()); + + assertEquals(r, r2); + assertEquals(r.getTheme(), r3.getTheme()); + + assertTrue(r3.getAllowedAttributes().contains("Test")); + + r.setAllowedAttributes(new ArrayList()); + this.dao.save(r); + final RegisteredService r4 = this.dao.findServiceById(r.getId()); + assertTrue(r4.getAllowedAttributes().isEmpty()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/MockRegisteredService.java b/cas-server-core/src/test/java/org/jasig/cas/services/MockRegisteredService.java new file mode 100644 index 0000000..e02b7a6 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/MockRegisteredService.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package org.jasig.cas.services; + +import org.jasig.cas.authentication.principal.Service; + +public class MockRegisteredService extends AbstractRegisteredService { + private static final long serialVersionUID = 4036877894594884813L; + + public boolean matches(Service service) { + return true; + } + + @Override + public void setServiceId(String id) { + this.serviceId = id; + } + + @Override + protected AbstractRegisteredService newInstance() { + return new MockRegisteredService(); + } +} 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 0000000..18a02a1 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/RegexRegisteredServiceTests.java @@ -0,0 +1,133 @@ +/* + * 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. + */ +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 + * @version $Revision: $ + */ +@RunWith(Parameterized.class) +public class RegexRegisteredServiceTests { + + private RegexRegisteredService service; + + private String serviceToMatch; + + private boolean expected; + + + @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, + }, + }); + } + + + public RegexRegisteredServiceTests( + final RegexRegisteredService service, + final String serviceToMatch, + final boolean expectedResult) { + this.service = service; + this.serviceToMatch = serviceToMatch; + this.expected = expectedResult; + } + + + @Test + public void testMatches() 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 0000000..6de72aa --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/RegisteredServiceImplTests.java @@ -0,0 +1,103 @@ +/* + * 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. + */ +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 + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +@RunWith(Parameterized.class) +public class RegisteredServiceImplTests { + + private RegisteredServiceImpl service; + + private String serviceToMatch; + + private boolean expected; + + + @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, + }, + }); + } + + + public RegisteredServiceImplTests( + final RegisteredServiceImpl service, + final String serviceToMatch, + final boolean expectedResult) { + this.service = service; + this.serviceToMatch = serviceToMatch; + this.expected = expectedResult; + } + + + @Test + public void testMatches() 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 0000000..b5be053 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedProxyingExceptionTests.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import junit.framework.TestCase; + + +public class UnauthorizedProxyingExceptionTests extends TestCase { + + private static final String CODE = "service.not.authorized.proxy"; + + public void testGetCode() { + UnauthorizedProxyingException e = new UnauthorizedProxyingException(); + assertEquals(CODE, e.getMessage()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final UnauthorizedProxyingException e = new UnauthorizedProxyingException(MESSAGE); + + assertEquals(MESSAGE, e.getMessage()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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 0000000..f34f78d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedServiceExceptionTests.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import junit.framework.TestCase; + + +public class UnauthorizedServiceExceptionTests extends TestCase { + + private static final String CODE = "service.not.authorized"; + + public void testGetCode() { + UnauthorizedServiceException e = new UnauthorizedServiceException(); + assertEquals(CODE, e.getMessage()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final UnauthorizedServiceException e = new UnauthorizedServiceException(MESSAGE); + + assertEquals(MESSAGE, e.getMessage()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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 0000000..c985deb --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/UnauthorizedSsoServiceExceptionTests.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +package org.jasig.cas.services; + +import junit.framework.TestCase; + + +public class UnauthorizedSsoServiceExceptionTests extends TestCase { + + private static final String CODE = "service.not.authorized.sso"; + + public void testGetCode() { + UnauthorizedSsoServiceException e = new UnauthorizedSsoServiceException(); + assertEquals(CODE, e.getMessage()); + } + + public void testCodeConstructor() { + final String MESSAGE = "GG"; + final UnauthorizedSsoServiceException e = new UnauthorizedSsoServiceException(MESSAGE); + + assertEquals(MESSAGE, e.getMessage()); + } + + public void testThrowableConstructorWithCode() { + final String MESSAGE = "GG"; + 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/web/ManageRegisteredServicesMultiActionControllerTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionControllerTests.java new file mode 100644 index 0000000..50a9777 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/web/ManageRegisteredServicesMultiActionControllerTests.java @@ -0,0 +1,177 @@ +/* + * 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. + */ +package org.jasig.cas.services.web; + +import java.util.Collection; + +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.ServicesManager; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.ModelAndView; + +import static org.junit.Assert.*; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class ManageRegisteredServicesMultiActionControllerTests { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private ManageRegisteredServicesMultiActionController controller; + + private ServicesManager servicesManager; + + @Before + public void setUp() throws Exception { + this.servicesManager = new DefaultServicesManagerImpl(new InMemoryServiceRegistryDaoImpl()); + this.controller = new ManageRegisteredServicesMultiActionController(this.servicesManager, "foo"); + } + + @Test + public void testDeleteService() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1200); + r.setName("name"); + r.setServiceId("serviceId"); + r.setEvaluationOrder(1); + + this.servicesManager.save(r); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("id", "1200"); + + final ModelAndView modelAndView = this.controller.deleteRegisteredService(request, new MockHttpServletResponse()); + + assertNotNull(modelAndView); + assertNull(this.servicesManager.findServiceBy(1200)); + assertEquals("deleted", modelAndView.getModel().get("status")); + assertEquals("name", modelAndView.getModelMap().get("serviceName")); + } + + public void testDeleteServiceNoService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("id", "1200"); + + final ModelAndView modelAndView = this.controller.deleteRegisteredService(request, new MockHttpServletResponse()); + + assertNotNull(modelAndView); + assertNull(this.servicesManager.findServiceBy(1200)); + assertEquals("deleted", modelAndView.getModel().get("status")); + assertEquals("", modelAndView.getModelMap().get("serviceName")); + } + + public void testManage() { + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1200); + r.setName("name"); + r.setServiceId("test"); + r.setEvaluationOrder(2); + + this.servicesManager.save(r); + + final ModelAndView modelAndView = this.controller.manage(new MockHttpServletRequest(), new MockHttpServletResponse()); + + assertNotNull(modelAndView); + assertEquals("manageServiceView", modelAndView.getViewName()); + + final Collection c = (Collection) modelAndView.getModel().get("services"); + assertTrue(c.contains(r)); + } + + @Test + public void updateEvaluationOrderOK() { + RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1200); + r.setName("name"); + r.setServiceId("test"); + r.setEvaluationOrder(2); + + this.servicesManager.save(r); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("id", String.valueOf(r.getId())); + request.addParameter("evaluationOrder", "100"); + + final ModelAndView modelAndView = this.controller.updateRegisteredServiceEvaluationOrder(request, new MockHttpServletResponse()); + + assertNotNull(modelAndView); + assertEquals("jsonView", modelAndView.getViewName()); + + RegisteredService result = this.servicesManager.findServiceBy(r.getId()); + assertEquals(result.getEvaluationOrder(), 100); + } + + @Test + public void updateEvaluationOrderInvalidServiceId() { + RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1200); + r.setName("name"); + r.setServiceId("test"); + r.setEvaluationOrder(2); + + this.servicesManager.save(r); + + try { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("id", "5000"); + request.addParameter("evaluationOrder", "1000"); + + this.controller.updateRegisteredServiceEvaluationOrder(request, new MockHttpServletResponse()); + } catch (IllegalArgumentException e) { + //Exception expected; service id cannot be found + log.debug(e.getMessage(), e); + } + } + + @Test + public void updateEvaluationOrderInvalidEvalOrder() { + RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1200); + r.setName("name"); + r.setServiceId("test"); + r.setEvaluationOrder(2); + + this.servicesManager.save(r); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("id", "1000"); + request.addParameter("evaluationOrder", "TEST"); + + try { + this.controller.updateRegisteredServiceEvaluationOrder(request, new MockHttpServletResponse()); + } catch (IllegalArgumentException e) { + //Exception expected; evaluation order is invalid + log.debug(e.getMessage(), e); + } + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormControllerTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormControllerTests.java new file mode 100644 index 0000000..929086a --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/web/RegisteredServiceSimpleFormControllerTests.java @@ -0,0 +1,294 @@ +/* + * 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. + */ +package org.jasig.cas.services.web; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.jasig.cas.services.DefaultServicesManagerImpl; +import org.jasig.cas.services.InMemoryServiceRegistryDaoImpl; +import org.jasig.cas.services.MockRegisteredService; +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.cas.services.web.support.RegisteredServiceValidator; +import org.jasig.services.persondir.support.StubPersonAttributeDao; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.validation.BindingResult; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + */ +public class RegisteredServiceSimpleFormControllerTests extends TestCase { + + private RegisteredServiceSimpleFormController controller; + + private ServicesManager manager; + + private StubPersonAttributeDao repository; + + @Override + protected void setUp() throws Exception { + final Map> attributes = new HashMap>(); + attributes.put("test", Arrays.asList(new Object[] {"test"})); + + this.repository = new StubPersonAttributeDao(); + this.repository.setBackingMap(attributes); + + this.manager = new DefaultServicesManagerImpl( + new InMemoryServiceRegistryDaoImpl()); + + final RegisteredServiceValidator validator = new RegisteredServiceValidator(); + validator.setServicesManager(this.manager); + + this.controller = new RegisteredServiceSimpleFormController( + this.manager, this.repository); + this.controller.setCommandName("registeredService"); + this.controller.setValidator(validator); + } + + public void testAddRegisteredServiceNoValues() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + request.setMethod("POST"); + + final ModelAndView modelAndView = this.controller.handleRequest( + request, response); + + final BindingResult result = (BindingResult) modelAndView + .getModel() + .get( + "org.springframework.validation.BindingResult.registeredService"); + + assertTrue(result.hasErrors()); + } + + public void testAddRegisteredServiceWithValues() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + request.addParameter("description", "description"); + request.addParameter("serviceId", "serviceId"); + request.addParameter("name", "name"); + request.addParameter("theme", "theme"); + request.addParameter("allowedToProxy", "true"); + request.addParameter("enabled", "true"); + request.addParameter("ssoEnabled", "true"); + request.addParameter("anonymousAccess", "false"); + request.addParameter("evaluationOrder", "1"); + + request.setMethod("POST"); + + assertTrue(this.manager.getAllServices().isEmpty()); + + this.controller.handleRequest( + request, response); + + final Collection services = this.manager.getAllServices(); + assertEquals(1, services.size()); + for(RegisteredService rs : this.manager.getAllServices()) { + assertTrue(rs instanceof RegisteredServiceImpl); + } + } + + public void testEditRegisteredServiceWithValues() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setId(1000); + r.setName("Test Service"); + r.setServiceId("test"); + r.setDescription("description"); + + this.manager.save(r); + + request.addParameter("description", "description"); + request.addParameter("serviceId", "serviceId1"); + request.addParameter("name", "name"); + request.addParameter("theme", "theme"); + request.addParameter("allowedToProxy", "true"); + request.addParameter("enabled", "true"); + request.addParameter("ssoEnabled", "true"); + request.addParameter("anonymousAccess", "false"); + request.addParameter("evaluationOrder", "2"); + request.addParameter("id", "1000"); + + request.setMethod("POST"); + + this.controller.handleRequest( + request, response); + + assertFalse(this.manager.getAllServices().isEmpty()); + final RegisteredService r2 = this.manager.findServiceBy(1000); + + assertEquals("serviceId1", r2.getServiceId()); + } + + // CAS-1071 test + public void testAddRegexRegisteredService() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + request.addParameter("description", "description"); + request.addParameter("serviceId", "^https://.*"); + request.addParameter("name", "name"); + request.addParameter("theme", "theme"); + request.addParameter("allowedToProxy", "true"); + request.addParameter("enabled", "true"); + request.addParameter("ssoEnabled", "true"); + request.addParameter("anonymousAccess", "false"); + request.addParameter("evaluationOrder", "1"); + + request.setMethod("POST"); + + assertTrue(this.manager.getAllServices().isEmpty()); + + this.controller.handleRequest( + request, response); + + final Collection services = this.manager.getAllServices(); + assertEquals(1, services.size()); + for(RegisteredService rs : this.manager.getAllServices()) { + assertTrue(rs instanceof RegexRegisteredService); + } + } + + // CAS-1071 test + public void testAddMultipleRegisteredServiceTypes() throws Exception { + final MockHttpServletRequest request1 = new MockHttpServletRequest(); + final MockHttpServletResponse response1 = new MockHttpServletResponse(); + + request1.addParameter("description", "description"); + request1.addParameter("serviceId", "serviceId"); + request1.addParameter("name", "ant"); + request1.addParameter("theme", "theme"); + request1.addParameter("allowedToProxy", "true"); + request1.addParameter("enabled", "true"); + request1.addParameter("ssoEnabled", "true"); + request1.addParameter("anonymousAccess", "false"); + request1.addParameter("evaluationOrder", "1"); + + request1.setMethod("POST"); + + final MockHttpServletRequest request2 = new MockHttpServletRequest(); + final MockHttpServletResponse response2 = new MockHttpServletResponse(); + + request2.addParameter("description", "description"); + request2.addParameter("serviceId", "^https://.*"); + request2.addParameter("name", "regex"); + request2.addParameter("theme", "theme"); + request2.addParameter("allowedToProxy", "true"); + request2.addParameter("enabled", "true"); + request2.addParameter("ssoEnabled", "true"); + request2.addParameter("anonymousAccess", "false"); + request2.addParameter("evaluationOrder", "1"); + + request2.setMethod("POST"); + + assertTrue(this.manager.getAllServices().isEmpty()); + + this.controller.handleRequest(request1, response1); + this.controller.handleRequest(request2, response2); + + final Collection services = this.manager.getAllServices(); + assertEquals(2, services.size()); + for(RegisteredService rs : this.manager.getAllServices()) { + if(rs.getName().equals("ant")) { + assertTrue(rs instanceof RegisteredServiceImpl); + }else if (rs.getName().equals("regex")) { + assertTrue(rs instanceof RegexRegisteredService); + } + } + } + + // test arbitrary RegisteredService subclass + public void testAddMockRegisteredService() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + request.addParameter("description", "description"); + request.addParameter("serviceId", "serviceId"); + request.addParameter("name", "name"); + request.addParameter("theme", "theme"); + request.addParameter("allowedToProxy", "true"); + request.addParameter("enabled", "true"); + request.addParameter("ssoEnabled", "true"); + request.addParameter("anonymousAccess", "false"); + request.addParameter("evaluationOrder", "1"); + + request.setMethod("POST"); + + assertTrue(this.manager.getAllServices().isEmpty()); + + this.controller.setCommandClass(MockRegisteredService.class); + this.controller.handleRequest(request, response); + + final Collection services = this.manager.getAllServices(); + assertEquals(1, services.size()); + for(RegisteredService rs : this.manager.getAllServices()) { + assertTrue(rs instanceof MockRegisteredService); + } + } + + public void testEditMockRegisteredService() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + final MockRegisteredService r = new MockRegisteredService(); + r.setId(1000); + r.setName("Test Service"); + r.setServiceId("test"); + r.setDescription("description"); + + this.manager.save(r); + + request.addParameter("description", "description"); + request.addParameter("serviceId", "serviceId1"); + request.addParameter("name", "name"); + request.addParameter("theme", "theme"); + request.addParameter("allowedToProxy", "true"); + request.addParameter("enabled", "true"); + request.addParameter("ssoEnabled", "true"); + request.addParameter("anonymousAccess", "false"); + request.addParameter("evaluationOrder", "2"); + request.addParameter("id", "1000"); + + request.setMethod("POST"); + + this.controller.handleRequest(request, response); + + assertFalse(this.manager.getAllServices().isEmpty()); + final RegisteredService r2 = this.manager.findServiceBy(1000); + + assertEquals("serviceId1", r2.getServiceId()); + assertTrue(r2 instanceof MockRegisteredService); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/services/web/ServiceThemeResolverTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/web/ServiceThemeResolverTests.java new file mode 100644 index 0000000..8e65e6d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/web/ServiceThemeResolverTests.java @@ -0,0 +1,93 @@ +/* + * 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. + */ +package org.jasig.cas.services.web; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +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.jasig.cas.web.support.ArgumentExtractor; +import org.jasig.cas.web.support.CasArgumentExtractor; +import org.springframework.mock.web.MockHttpServletRequest; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class ServiceThemeResolverTests extends TestCase { + + private ServiceThemeResolver serviceThemeResolver; + + private ServicesManager servicesManager; + + protected void setUp() throws Exception { + this.servicesManager = new DefaultServicesManagerImpl(new InMemoryServiceRegistryDaoImpl()); + + this.serviceThemeResolver = new ServiceThemeResolver(); + this.serviceThemeResolver.setDefaultThemeName("test"); + this.serviceThemeResolver.setServicesManager(this.servicesManager); + this.serviceThemeResolver.setArgumentExtractors(Arrays.asList((ArgumentExtractor) new CasArgumentExtractor())); + final Map mobileBrowsers = new HashMap(); + mobileBrowsers.put("Mozilla", "theme"); + this.serviceThemeResolver.setMobileBrowsers(mobileBrowsers); + } + + public void testGetServiceTheme() { + 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(); + request.setParameter("service", "myServiceId"); + request.addHeader("User-Agent", "Mozilla"); + System.out.println("1"); + assertEquals("myTheme", this.serviceThemeResolver.resolveThemeName(request)); + } + + public void testGetDefaultService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("service", "myServiceId"); + request.addHeader("User-Agent", "Mozilla"); + assertEquals("test", this.serviceThemeResolver.resolveThemeName(request)); + } + + public void testGetDefaultServiceWithNoServicesManager() { + 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-core/src/test/java/org/jasig/cas/services/web/support/RegisteredServiceValidatorTests.java b/cas-server-core/src/test/java/org/jasig/cas/services/web/support/RegisteredServiceValidatorTests.java new file mode 100644 index 0000000..299d229 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/services/web/support/RegisteredServiceValidatorTests.java @@ -0,0 +1,140 @@ +/* + * 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. + */ +package org.jasig.cas.services.web.support; + +import java.util.ArrayList; +import java.util.Collection; + +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.services.RegisteredService; +import org.jasig.cas.services.RegisteredServiceImpl; +import org.jasig.cas.services.ServicesManager; +import org.springframework.validation.BindException; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class RegisteredServiceValidatorTests extends TestCase { + + private RegisteredServiceValidator validator; + + protected void setUp() throws Exception { + this.validator = new RegisteredServiceValidator(); + this.validator.setMaxDescriptionLength(1); + } + + public void testIdExists() { + checkId(true, 1, "test"); + } + + public void testIdDoesNotExist() { + checkId(false, 0, "test"); + } + + public void testIdDoesNotExist2() { + checkId(true, 0, "test2"); + } + + public void testIdDoesNotExist3() { + checkId(true, 0, null); + } + + public void testSupports() { + assertTrue(this.validator.supports(RegisteredServiceImpl.class)); + assertFalse(this.validator.supports(Object.class)); + } + + + public void testMaxLength() { + this.validator.setServicesManager(new TestServicesManager(false)); + final RegisteredServiceImpl impl = new RegisteredServiceImpl(); + impl.setServiceId("test"); + impl.setDescription("fasdfdsafsafsafdsa"); + + final BindException exception = new BindException(impl, "registeredService"); + + this.validator.validate(impl, exception); + + assertEquals(1, exception.getErrorCount()); + } + + protected void checkId(final boolean exists, final int expectedErrors, final String name) { + this.validator.setServicesManager(new TestServicesManager(exists)); + final RegisteredServiceImpl impl = new RegisteredServiceImpl(); + impl.setServiceId(name); + + final BindException exception = new BindException(impl, "registeredService"); + + this.validator.validate(impl, exception); + + assertEquals(expectedErrors, exception.getErrorCount()); + + } + + + + protected class TestServicesManager implements ServicesManager { + + private final boolean returnValue; + + protected TestServicesManager(final boolean returnValue) { + this.returnValue = returnValue; + } + + public RegisteredService delete(long id) { + return null; + } + + public RegisteredService findServiceBy(long id) { + return null; + } + + public RegisteredService findServiceBy(Service service) { + return null; + } + + public Collection getAllServices() { + if (!this.returnValue) { + return new ArrayList(); + } + final RegisteredServiceImpl r = new RegisteredServiceImpl(); + r.setServiceId("test"); + r.setId(1000); + + final ArrayList list = new ArrayList(); + list.add(r); + + return list; + } + + public boolean matchesExistingService(final Service service) { + return this.returnValue; + } + + public RegisteredService save(final RegisteredService registeredService) { + return null; + } + } +} 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 0000000..8df5d41 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/InvalidTicketExceptionTests.java @@ -0,0 +1,39 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.handler.BadCredentialsAuthenticationException; + +import junit.framework.TestCase; + +public class InvalidTicketExceptionTests extends TestCase { + + public void testCodeNoThrowable() { + TicketException t = new InvalidTicketException(); + assertEquals("INVALID_TICKET", t.getCode()); + } + + public void testCodeWithThrowable() { + AuthenticationException a = new BadCredentialsAuthenticationException(); + TicketException t = new InvalidTicketException(a); + + 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 0000000..e98b919 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/ServiceTicketImplTests.java @@ -0,0 +1,150 @@ +/* + * 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. + */ +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.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy; +import org.jasig.cas.ticket.support.NeverExpiresExpirationPolicy; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.UniqueTicketIdGenerator; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class ServiceTicketImplTests extends TestCase { + + private TicketGrantingTicketImpl ticketGrantingTicket = new TicketGrantingTicketImpl( + "test", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + + private UniqueTicketIdGenerator uniqueTicketIdGenerator = new DefaultUniqueTicketIdGenerator(); + + public void testNoService() { + try { + new ServiceTicketImpl("stest1", this.ticketGrantingTicket, null, + true, new NeverExpiresExpirationPolicy()); + fail("Exception expected."); + } catch (Exception e) { + // ok + } + } + + public void testNoTicket() { + try { + new ServiceTicketImpl("stest1", null, TestUtils.getService(), + true, new NeverExpiresExpirationPolicy()); + fail("Exception expected."); + } catch (Exception e) { + // ok + } + } + + public void testIsFromNewLoginTrue() { + ServiceTicket s = new ServiceTicketImpl("stest1", + this.ticketGrantingTicket, TestUtils.getService(), true, + new NeverExpiresExpirationPolicy()); + assertTrue(s.isFromNewLogin()); + } + + public void testIsFromNewLoginFalse() { + ServiceTicket s = new ServiceTicketImpl("stest1", + this.ticketGrantingTicket, TestUtils.getService(), false, + new NeverExpiresExpirationPolicy()); + assertFalse(s.isFromNewLogin()); + } + + public void testGetService() { + Service simpleService = TestUtils.getService(); + ServiceTicket s = new ServiceTicketImpl("stest1", + this.ticketGrantingTicket, simpleService, false, + new NeverExpiresExpirationPolicy()); + assertEquals(simpleService, s.getService()); + } + + public void testGetTicket() { + Service simpleService = TestUtils.getService(); + ServiceTicket s = new ServiceTicketImpl("stest1", + this.ticketGrantingTicket, simpleService, false, + new NeverExpiresExpirationPolicy()); + assertEquals(this.ticketGrantingTicket, s.getGrantingTicket()); + } + + public void testIsExpiredTrueBecauseOfRoot() { + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), new NeverExpiresExpirationPolicy()); + ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + t.expire(); + + assertTrue(s.isExpired()); + } + + public void testIsExpiredFalse() { + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), new NeverExpiresExpirationPolicy()); + ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new MultiTimeUseOrTimeoutExpirationPolicy(1, 5000), false); + assertFalse(s.isExpired()); + } + + public void testTicketGrantingTicket() { + Authentication a = TestUtils.getAuthentication(); + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), new NeverExpiresExpirationPolicy()); + ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new MultiTimeUseOrTimeoutExpirationPolicy(1, 5000), false); + TicketGrantingTicket t1 = s.grantTicketGrantingTicket( + this.uniqueTicketIdGenerator + .getNewTicketId(TicketGrantingTicket.PREFIX), a, + new NeverExpiresExpirationPolicy()); + + assertEquals(a, t1.getAuthentication()); + } + + public void testTicketGrantingTicketGrantedTwice() { + Authentication a = TestUtils.getAuthentication(); + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), new NeverExpiresExpirationPolicy()); + 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 (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 0000000..b9726ef --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketCreationExceptionTests.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class TicketCreationExceptionTests extends TestCase { + + public void testNoParamConstructor() { + new TicketCreationException(); + } + + public void testThrowableParamConstructor() { + final Throwable THROWABLE = new Throwable(); + 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 0000000..10cac3b --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketGrantingTicketImplTests.java @@ -0,0 +1,161 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import java.util.ArrayList; +import java.util.List; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +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 junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class TicketGrantingTicketImplTests extends TestCase { + + private UniqueTicketIdGenerator uniqueTicketIdGenerator = new DefaultUniqueTicketIdGenerator(); + + public void testEquals() { + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + assertFalse(t.equals(null)); + assertFalse(t.equals(new Object())); + assertTrue(t.equals(t)); + } + + public void testNullAuthentication() { + try { + new TicketGrantingTicketImpl("test", null, null, + new NeverExpiresExpirationPolicy()); + fail("Exception expected."); + } catch (Exception e) { + // this is okay + } + } + + public void testGetAuthentication() { + Authentication authentication = TestUtils.getAuthentication(); + + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + authentication, new NeverExpiresExpirationPolicy()); + + assertEquals(t.getAuthentication(), authentication); + assertEquals(t.getId(), t.toString()); + } + + public void testIsRootTrue() { + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + assertTrue(t.isRoot()); + } + + public void testIsRootFalse() { + TicketGrantingTicketImpl t1 = new TicketGrantingTicketImpl("test", null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", t1, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + + assertFalse(t.isRoot()); + } + + public void testGetChainedPrincipalsWithOne() { + Authentication authentication = TestUtils.getAuthentication(); + List principals = new ArrayList(); + principals.add(authentication); + + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + authentication, new NeverExpiresExpirationPolicy()); + + assertEquals(principals, t.getChainedAuthentications()); + } + + public void testCheckCreationTime() { + Authentication authentication = TestUtils.getAuthentication(); + List principals = new ArrayList(); + principals.add(authentication); + + final long startTime = System.currentTimeMillis(); + final TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + authentication, new NeverExpiresExpirationPolicy()); + final long finishTime = System.currentTimeMillis(); + + assertTrue(startTime <= t.getCreationTime() && finishTime >= t.getCreationTime()); + } + + public void testGetChainedPrincipalsWithTwo() { + Authentication authentication = TestUtils.getAuthentication(); + Authentication authentication1 = TestUtils.getAuthentication("test1"); + List principals = new ArrayList(); + principals.add(authentication); + principals.add(authentication1); + + TicketGrantingTicketImpl t1 = new TicketGrantingTicketImpl("test", null, + authentication1, new NeverExpiresExpirationPolicy()); + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", t1, + authentication, new NeverExpiresExpirationPolicy()); + + assertEquals(principals, t.getChainedAuthentications()); + } + + public void testServiceTicketAsFromInitialCredentials() { + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + + assertTrue(s.isFromNewLogin()); + } + + public void testServiceTicketAsFromNotInitialCredentials() { + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + ServiceTicket s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + s = t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), TestUtils.getService(), + new NeverExpiresExpirationPolicy(), false); + + assertFalse(s.isFromNewLogin()); + } + + public void testWebApplicationSignOut() { + final MockService testService = new MockService("test"); + TicketGrantingTicket t = new TicketGrantingTicketImpl("test", null, + TestUtils.getAuthentication(), new NeverExpiresExpirationPolicy()); + t.grantServiceTicket(this.uniqueTicketIdGenerator + .getNewTicketId(ServiceTicket.PREFIX), testService, + new NeverExpiresExpirationPolicy(), false); + + t.expire(); + + assertTrue(testService.isLoggedOut()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketValidationExceptionTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketValidationExceptionTests.java new file mode 100644 index 0000000..392a1a4 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/TicketValidationExceptionTests.java @@ -0,0 +1,46 @@ +/* + * 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. + */ +package org.jasig.cas.ticket; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Service; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class TicketValidationExceptionTests extends TestCase { + + private static final String CODE = "INVALID_SERVICE"; + + private Service service = TestUtils.getService(); + + + public void testThrowableConstructor() { + final TicketValidationException t = new TicketValidationException(this.service); + + assertSame(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 0000000..1d27bf5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas10ProxyHandlerTests.java @@ -0,0 +1,43 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.proxy.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.proxy.ProxyHandler; +import org.jasig.cas.ticket.proxy.support.Cas10ProxyHandler; +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ Date$ + * @since 3.0 + */ +public class Cas10ProxyHandlerTests extends TestCase { + + private ProxyHandler proxyHandler = new Cas10ProxyHandler(); + + public void testNoCredentialsOrProxy() { + assertNull(this.proxyHandler.handle(null, null)); + } + + public void testCredentialsAndProxy() { + assertNull(this.proxyHandler.handle(TestUtils + .getCredentialsWithSameUsernameAndPassword(), "test")); + } +} 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 0000000..28c8181 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/proxy/support/Cas20ProxyHandlerTests.java @@ -0,0 +1,62 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.proxy.support; + +import java.net.URL; + +import org.jasig.cas.authentication.principal.HttpBasedServiceCredentials; +import org.jasig.cas.util.DefaultUniqueTicketIdGenerator; +import org.jasig.cas.util.HttpClient; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class Cas20ProxyHandlerTests extends TestCase { + + private Cas20ProxyHandler handler; + + protected void setUp() throws Exception { + this.handler = new Cas20ProxyHandler(); + this.handler.setHttpClient(new HttpClient()); + this.handler.setUniqueTicketIdGenerator(new DefaultUniqueTicketIdGenerator()); + } + + public void testValidProxyTicketWithoutQueryString() throws Exception { + assertNotNull(this.handler.handle(new HttpBasedServiceCredentials( + new URL("http://www.rutgers.edu/")), "proxyGrantingTicketId")); + } + + public void testValidProxyTicketWithQueryString() throws Exception { + assertNotNull(this.handler.handle(new HttpBasedServiceCredentials( + new URL("http://www.rutgers.edu/?test=test")), + "proxyGrantingTicketId")); + } + + public void testNonValidProxyTicket() throws Exception { + final HttpClient httpClient = new HttpClient(); + httpClient.setAcceptableCodes(new int[] {900}); + this.handler.setHttpClient(httpClient); + assertNull(this.handler.handle(new HttpBasedServiceCredentials(new URL( + "http://www.rutgers.edu")), "proxyGrantingTicketId")); + } +} 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 0000000..336adc7 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractRegistryCleanerTests.java @@ -0,0 +1,85 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry; + +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 junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public abstract class AbstractRegistryCleanerTests extends TestCase { + + private RegistryCleaner registryCleaner; + + private TicketRegistry ticketRegistry; + + /** + * @see junit.framework.TestCase#setUp() + */ + protected void setUp() throws Exception { + super.setUp(); + this.ticketRegistry = this.getNewTicketRegistry(); + this.registryCleaner = this.getNewRegistryCleaner(this.ticketRegistry); + } + + public abstract RegistryCleaner getNewRegistryCleaner( + TicketRegistry newTicketRegistry); + + public abstract TicketRegistry getNewTicketRegistry(); + + public void testCleanEmptyTicketRegistry() { + this.registryCleaner.clean(); + assertTrue(this.ticketRegistry.getTickets().isEmpty()); + } + + public void testCleanRegistryOfExpiredTicketsAllExpired() { + populateRegistryWithExpiredTickets(); + this.registryCleaner.clean(); + assertTrue(this.ticketRegistry.getTickets().isEmpty()); + } + + public void testCleanRegistryOneNonExpired() { + populateRegistryWithExpiredTickets(); + TicketGrantingTicket ticket = new TicketGrantingTicketImpl( + "testNoExpire", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + this.ticketRegistry.addTicket(ticket); + + this.registryCleaner.clean(); + + assertEquals(this.ticketRegistry.getTickets().size(), 1); + } + + private void populateRegistryWithExpiredTickets() { + for (int i = 0; i < 10; i++) { + TicketGrantingTicket ticket = new TicketGrantingTicketImpl("test" + + i, TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + ticket.expire(); + this.ticketRegistry.addTicket(ticket); + } + } +} 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 0000000..5ce2ec4 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/AbstractTicketRegistryTests.java @@ -0,0 +1,219 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry; + +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 junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public abstract class AbstractTicketRegistryTests extends TestCase { + + private static final int TICKETS_IN_REGISTRY = 10; + + private TicketRegistry ticketRegistry; + + protected void setUp() throws Exception { + super.setUp(); + this.ticketRegistry = this.getNewTicketRegistry(); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Abstract method to retrieve a new ticket registry. Implementing classes + * return the TicketRegistry they wish to test. + * + * @return the TicketRegistry we wish to test + */ + 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. + */ + public void testAddTicketToCache() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + } catch (Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testGetNullTicket() { + try { + this.ticketRegistry.getTicket(null, TicketGrantingTicket.class); + } catch (Exception e) { + fail("Exception caught. None expected."); + } + } + + public void testGetNonExistingTicket() { + try { + this.ticketRegistry.getTicket("FALALALALALAL", + TicketGrantingTicket.class); + } catch (Exception e) { + fail("Exception caught. None expected."); + } + } + + public void testGetExistingTicketWithProperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", TicketGrantingTicket.class); + } catch (Exception e) { + System.out.println(e); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testGetExistingTicketWithInproperClass() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST", ServiceTicket.class); + } catch (ClassCastException e) { + return; + } + fail("ClassCastException expected."); + } + + public void testGetNullTicketWithoutClass() { + try { + this.ticketRegistry.getTicket(null); + } catch (Exception e) { + fail("Exception caught. None expected."); + } + } + + public void testGetNonExistingTicketWithoutClass() { + try { + this.ticketRegistry.getTicket("FALALALALALAL"); + } catch (Exception e) { + fail("Exception caught. None expected."); + } + } + + public void testGetExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + this.ticketRegistry.getTicket("TEST"); + } catch (Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testDeleteExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertTrue("Ticket was not deleted.", this.ticketRegistry + .deleteTicket("TEST")); + } catch (Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testDeleteNonExistingTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry + .deleteTicket("TEST1")); + } catch (Exception e) { + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testDeleteNullTicket() { + try { + this.ticketRegistry.addTicket(new TicketGrantingTicketImpl("TEST", + TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy())); + assertFalse("Ticket was deleted.", this.ticketRegistry + .deleteTicket(null)); + } catch (Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testGetTicketsIsZero() { + try { + assertEquals("The size of the empty registry is not zero.", + this.ticketRegistry.getTickets().size(), 0); + } catch (Exception e) { + e.printStackTrace(); + fail("Caught an exception. But no exception should have been thrown."); + } + } + + public void testGetTicketsFromRegistryEqualToTicketsAdded() { + 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 { + 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 (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 0000000..1a80e6f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DefaultTicketRegistryTests.java @@ -0,0 +1,38 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry; + +/** + * Test case to test the DefaultTicketRegistry based on test cases to test all + * Ticket Registries. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class DefaultTicketRegistryTests extends AbstractTicketRegistryTests { + + public TicketRegistry getNewTicketRegistry() throws Exception { + return new DefaultTicketRegistry(); + } + + public void testOtherConstructor() { + 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 0000000..015c240 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/DistributedTicketRegistryTests.java @@ -0,0 +1,136 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +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 junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class DistributedTicketRegistryTests extends TestCase { + + private TestDistributedTicketRegistry ticketRegistry; + + public boolean wasTicketUpdated = false; + + protected void setUp() throws Exception { + this.ticketRegistry = new TestDistributedTicketRegistry(); + this.wasTicketUpdated = false; + } + + public void testProxiedInstancesEqual() { + 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()); + } + + public void testUpdateOfRegistry() { + 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.expire(); + assertTrue(t.isExpired()); + } + + public void testTicketDoesntExist() { + assertNull(this.ticketRegistry.getTicket("fdfas")); + } + + protected class TestDistributedTicketRegistry extends AbstractDistributedTicketRegistry { + + private Map tickets = new HashMap(); + + protected void updateTicket(final Ticket ticket) { + DistributedTicketRegistryTests.this.wasTicketUpdated = 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/JpaTicketRegistryTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/JpaTicketRegistryTests.java new file mode 100644 index 0000000..0d07071 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/JpaTicketRegistryTests.java @@ -0,0 +1,231 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry; + +import static org.junit.Assert.*; + +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 javax.sql.DataSource; + +import org.jasig.cas.authentication.ImmutableAuthentication; +import org.jasig.cas.authentication.principal.Principal; +import org.jasig.cas.authentication.principal.SimplePrincipal; +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.simple.SimpleJdbcTemplate; +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.SimpleJdbcTestUtils; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + + +/** + * Unit test for {@link JpaTicketRegistry} class. + * + * @author Marvin S. Addison + * @version $Revision: $ + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/jpaTestApplicationContext.xml") +@ProfileValueSourceConfiguration(SystemProfileValueSource.class) +public class JpaTicketRegistryTests { + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Number of clients contending for operations in concurrent test. */ + private static final int CONCURRENT_SIZE = 20; + + private static UniqueTicketIdGenerator idGenerator = new DefaultUniqueTicketIdGenerator(64); + + private static ExpirationPolicy expirationPolicyTGT = new HardTimeoutExpirationPolicy(1000); + + private static ExpirationPolicy expirationPolicyST = new MultiTimeUseOrTimeoutExpirationPolicy(1, 1000); + + @Autowired + private PlatformTransactionManager txManager; + + @Autowired + private JpaTicketRegistry jpaTicketRegistry; + + private SimpleJdbcTemplate simpleJdbcTemplate; + + + /** + * Set the datasource. + */ + @Autowired + public void setDataSource(final DataSource dataSource) { + this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + } + + + @Before + public void setUp() { + SimpleJdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "SERVICETICKET"); + SimpleJdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "TICKETGRANTINGTICKET"); + } + + + @Test + public void testTicketCreationAndDeletion() 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 testConcurrentServiceTicketGeneration() 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())); + } + final List> results = executor.invokeAll(generators); + for (Future result : results) { + assertNotNull(result.get()); + } + } catch (Exception e) { + logger.debug("testConcurrentServiceTicketGeneration produced an error", e); + fail("testConcurrentServiceTicketGeneration failed."); + } finally { + executor.shutdownNow(); + } + } + + + static TicketGrantingTicket newTGT() { + final Principal principal = new SimplePrincipal( + "bob", Collections.singletonMap("displayName", (Object) "Bob")); + return new TicketGrantingTicketImpl( + idGenerator.getNewTicketId("TGT"), + new ImmutableAuthentication(principal, null), + expirationPolicyTGT); + } + + static ServiceTicket newST(final TicketGrantingTicket parent) { + return parent.grantServiceTicket( + idGenerator.getNewTicketId("ST"), + new MockService("https://service.example.com"), + expirationPolicyST, + 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; + } + }); + } + + class ServiceTicketGenerator implements Callable { + + private String parentTgtId; + + public ServiceTicketGenerator(final String tgtId) { + parentTgtId = tgtId; + } + + /** {@inheritDoc} */ + 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, Credentials) + final ServiceTicket st = newST((TicketGrantingTicket) jpaTicketRegistry.getTicket(parentTgtId)); + jpaTicketRegistry.addTicket(st); + return st.getId(); + } + }); + } + + } +} 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 0000000..89d7362 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleanerTests.java @@ -0,0 +1,45 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry.support; + +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; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class DefaultTicketRegistryCleanerTests extends + AbstractRegistryCleanerTests { + + public RegistryCleaner getNewRegistryCleaner( + final TicketRegistry ticketRegistry) { + DefaultTicketRegistryCleaner cleaner = new DefaultTicketRegistryCleaner(); + cleaner.setTicketRegistry(ticketRegistry); + + return cleaner; + } + + public TicketRegistry getNewTicketRegistry() { + return new DefaultTicketRegistry(); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/JdbcLockingStrategyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/JdbcLockingStrategyTests.java new file mode 100644 index 0000000..47ba7dd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/JdbcLockingStrategyTests.java @@ -0,0 +1,116 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry.support; + +import javax.sql.DataSource; + +import junit.framework.TestCase; + +import org.jasig.cas.ticket.registry.support.JdbcLockingStrategy.DatabasePlatform; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.SimpleDriverDataSource; + +/** + * Unit test for {@link JdbcLockingStrategy} class. + * + * @author Marvin S. Addison + * @version $Revision$ + * @since 3.3.6 + * + */ +public class JdbcLockingStrategyTests extends TestCase { + /** DDL statement to create lock table */ + private static final String CREATE_TABLE_SQL = + "CREATE TABLE LOCKS (" + + "APPLICATION_ID VARCHAR(50) NOT NULL," + + "UNIQUE_ID VARCHAR(50) NULL," + + "EXPIRATION_DATE TIMESTAMP NULL)"; + + /** DDL statement to create primary key on table */ + private static final String CREATE_PRI_KEY_SQL = + "ALTER TABLE LOCKS ADD CONSTRAINT LOCKS_PK " + + "PRIMARY KEY (APPLICATION_ID)"; + + /** HSQL in-memory data source */ + private DataSource testDataSource; + + + /** + * @throws Exception on test setup. + */ + public void setUp() throws Exception { + super.setUp(); + this.testDataSource = new SimpleDriverDataSource( + new org.hsqldb.jdbcDriver(), + "jdbc:hsqldb:mem:locktest", + "sa", + ""); + final JdbcTemplate tmpl = new JdbcTemplate(this.testDataSource); + tmpl.execute(CREATE_TABLE_SQL); + tmpl.execute(CREATE_PRI_KEY_SQL); + } + + + /** + * Test method for {@link JdbcLockingStrategy#acquire()}. + * @throws Exception on test errors. + */ + public void testAcquireAndRelease() throws Exception { + final JdbcLockingStrategy lock1 = new JdbcLockingStrategy(); + final JdbcLockingStrategy lock2 = new JdbcLockingStrategy(); + lock1.setDataSource(this.testDataSource); + lock2.setDataSource(this.testDataSource); + lock1.setPlatform(DatabasePlatform.HSQL); + lock2.setPlatform(DatabasePlatform.HSQL); + lock1.setApplicationId("ticketregistry"); + lock2.setApplicationId("ticketregistry"); + lock1.setUniqueId("lock1"); + lock2.setUniqueId("lock2"); + lock1.setLockTimeout(2); + lock2.setLockTimeout(2); + lock1.afterPropertiesSet(); + lock2.afterPropertiesSet(); + + // Ensure initial acquisition works + assertTrue(lock1.acquire()); + assertFalse(lock2.acquire()); + + // Ensure locks are not re-entrant + assertFalse(lock1.acquire()); + + // Ensure someone else can acquire the lock + lock1.release(); + assertTrue(lock2.acquire()); + assertFalse(lock1.acquire()); + + // Another re-entrant check + assertFalse(lock2.acquire()); + + // Lock expiration check + // #2 has the lock currently + // Allow it to time out, then #1 should succeed acquiring lock + Thread.sleep(3000); + assertTrue(lock1.acquire()); + assertFalse(lock2.acquire()); + + lock1.release(); + lock2.release(); + } + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategyTests.java b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategyTests.java new file mode 100644 index 0000000..fd37e2b --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/registry/support/JpaLockingStrategyTests.java @@ -0,0 +1,329 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.registry.support; + +import static org.junit.Assert.*; + +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 javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +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.simple.SimpleJdbcTemplate; +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.SimpleJdbcTestUtils; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +/** + * Unit test for {@link JpaLockingStrategy}. + * + * @author Marvin S. Addison + * @version $Revision: $ + * + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("/jpaTestApplicationContext.xml") +@ProfileValueSourceConfiguration(SystemProfileValueSource.class) +public class JpaLockingStrategyTests implements InitializingBean { + /** Logger instance. */ + private final Logger logger = LoggerFactory.getLogger(getClass()); + + /** Number of clients contending for lock in concurrent test. */ + private static final int CONCURRENT_SIZE = 13; + + @Autowired + private PlatformTransactionManager txManager; + + @Autowired + private EntityManagerFactory factory; + + private SimpleJdbcTemplate simpleJdbcTemplate; + + /** + * Set the datasource. + */ + @Autowired + public void setDataSource(final DataSource dataSource) { + this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource); + } + + /** + * One-time test initialization. + * + * @throws Exception On setup errors. + */ + public void afterPropertiesSet() throws Exception { + SimpleJdbcTestUtils.deleteFromTables(simpleJdbcTemplate, "locks"); + } + + /** + * Test basic acquire/release semantics. + * + * @throws Exception On errors. + */ + @Test + public void testAcquireAndRelease() 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 (Exception e) { + logger.debug("testAcquireAndRelease produced an error", e); + fail("testAcquireAndRelease failed"); + } + } + + /** + * Test lock expiration. + * + * @throws Exception On errors. + */ + @Test + public void testLockExpiration() 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 (Exception e) { + logger.debug("testLockExpiration produced an error", e); + fail("testLockExpiration failed"); + } + } + + /** + * Verify non-reentrant behavior. + */ + @Test + public void testNonReentrantBehavior() { + 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 (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 testConcurrentAcquireAndRelease() throws Exception { + final ExecutorService executor = Executors.newFixedThreadPool(CONCURRENT_SIZE); + try { + testConcurrency(executor, getConcurrentLocks("concurrent-new")); + } catch (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 testConcurrentAcquireAndReleaseOnExistingLock() 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 (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)); + } + + 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 (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 (Future result : executor.invokeAll(lockers)) { + if (result.get()) { + releaseCount++; + } + } + assertTrue("Release count should be <= 1 but was " + releaseCount, releaseCount <= 1); + } + + class TransactionalLockInvocationHandler implements InvocationHandler { + private JpaLockingStrategy jpaLock; + + public TransactionalLockInvocationHandler(final JpaLockingStrategy lock) { + jpaLock = lock; + } + + public JpaLockingStrategy getLock() { + return jpaLock; + } + + /** {@inheritDoc} */ + 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 (Exception e) { + throw new RuntimeException("Transactional method invocation failed.", e); + } + } + }); + } + + } + + class Locker implements Callable { + + private LockingStrategy lock; + + public Locker(final LockingStrategy l) + { + lock = l; + } + + /** {@inheritDoc} */ + public Boolean call() throws Exception { + try { + return lock.acquire(); + } catch (Exception e) { + logger.debug("{} failed to acquire lock", lock, e); + return false; + } + } + } + + + class Releaser implements Callable { + + private LockingStrategy lock; + + public Releaser(final LockingStrategy l) + { + lock = l; + } + + /** {@inheritDoc} */ + public Boolean call() throws Exception { + try { + lock.release(); + return true; + } catch (Exception e) { + logger.debug("{} failed to release lock", lock, e); + return false; + } + } + } + +} 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 0000000..d089ec5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/MultiTimeUseOrTimeoutExpirationPolicyTests.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; + +import junit.framework.TestCase; + +import java.util.concurrent.TimeUnit; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class MultiTimeUseOrTimeoutExpirationPolicyTests extends TestCase { + + private static final long TIMEOUT_SECONDS = 5L; + + private static final long TIMEOUT_MILLISECONDS = 5000L; + + private static final int NUMBER_OF_USES = 5; + + private static final int TIMEOUT_BUFFER = 100; + + private ExpirationPolicy expirationPolicy; + + private TicketGrantingTicket ticket; + + protected void setUp() throws Exception { + this.expirationPolicy = new MultiTimeUseOrTimeoutExpirationPolicy( + NUMBER_OF_USES, TIMEOUT_SECONDS, TimeUnit.SECONDS); + + this.ticket = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), this.expirationPolicy); + + super.setUp(); + } + + public void testTicketIsNull() { + assertTrue(this.expirationPolicy.isExpired(null)); + } + + public void testTicketIsNotExpired() { + assertFalse(this.ticket.isExpired()); + } + + public void testTicketIsExpiredByTime() { + try { + Thread.sleep(TIMEOUT_MILLISECONDS + TIMEOUT_BUFFER); + assertTrue(this.ticket.isExpired()); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + + public void testTicketIsExpiredByCount() { + 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 0000000..58a1f4a --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/RememberMeDelegatingExpirationPolicyTests.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.MutableAuthentication; +import org.jasig.cas.authentication.principal.RememberMeCredentials; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; + +import junit.framework.TestCase; + +/** + * Tests for RememberMeDelegatingExpirationPolicy + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public final class RememberMeDelegatingExpirationPolicyTests extends TestCase { + + private RememberMeDelegatingExpirationPolicy p; + + protected void setUp() throws Exception { + this.p = new RememberMeDelegatingExpirationPolicy(); + this.p.setRememberMeExpirationPolicy(new MultiTimeUseOrTimeoutExpirationPolicy(1, 20000)); + this.p.setSessionExpirationPolicy(new MultiTimeUseOrTimeoutExpirationPolicy(5, 20000)); + } + + public void testTicketExpirationWithRememberMe() { + final MutableAuthentication authentication = new MutableAuthentication(TestUtils.getPrincipal()); + authentication.getAttributes().put(RememberMeCredentials.AUTHENTICATION_ATTRIBUTE_REMEMBER_ME, Boolean.TRUE); + final TicketGrantingTicketImpl t = new TicketGrantingTicketImpl("111", authentication, this.p); + assertFalse(t.isExpired()); + t.grantServiceTicket("55", TestUtils.getService(), this.p, false); + assertTrue(t.isExpired()); + + } + + public void testTicketExpirationWithoutRememberMe() { + final MutableAuthentication authentication = new MutableAuthentication(TestUtils.getPrincipal()); + 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 0000000..7861d01 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/ThrottledUseAndTimeoutExpirationPolicyTests.java @@ -0,0 +1,78 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class ThrottledUseAndTimeoutExpirationPolicyTests extends TestCase { + + private static final long TIMEOUT = 5000; + + private ThrottledUseAndTimeoutExpirationPolicy expirationPolicy; + + private TicketGrantingTicket ticket; + + protected void setUp() throws Exception { + this.expirationPolicy = new ThrottledUseAndTimeoutExpirationPolicy(); + this.expirationPolicy.setTimeToKillInMilliSeconds(TIMEOUT); + this.expirationPolicy.setTimeInBetweenUsesInMilliSeconds(1000); + + this.ticket = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), this.expirationPolicy); + + super.setUp(); + } + + public void testTicketIsNotExpired() { + assertFalse(this.ticket.isExpired()); + } + + public void testTicketIsExpired() { + try { + Thread.sleep(TIMEOUT + 100); + assertTrue(this.ticket.isExpired()); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + + public void testTicketUsedButWithTimeout() { + try { + this.ticket.grantServiceTicket("test", TestUtils.getService(), this.expirationPolicy, false); + Thread.sleep(TIMEOUT -100); + assertFalse(this.ticket.isExpired()); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } + + public void testNotWaitingEnoughTime() { + 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 0000000..e086c7f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TicketGrantingTicketExpirationPolicyTests.java @@ -0,0 +1,90 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import junit.framework.TestCase; +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.TicketGrantingTicket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; + +/** + * @author William G. Thompson, Jr. + * @version $Revision$ $Date$ + * @since 3.4.10 + */ +public class TicketGrantingTicketExpirationPolicyTests extends TestCase { + + private static final long HARD_TIMEOUT = 10000L; // 10s + + private static final long SLIDING_TIMEOUT = 2000L; // 2s + + private static final long HARD_TIMEOUT_SECONDS = 10L; + + private static final long SLIDING_TIMEOUT_SECONDS = 2L; + + private TicketGrantingTicketExpirationPolicy expirationPolicy; + + private TicketGrantingTicket ticketGrantingTicket; + + protected void setUp() throws Exception { + this.expirationPolicy = new TicketGrantingTicketExpirationPolicy(); + this.expirationPolicy.setMaxTimeToLiveInSeconds(HARD_TIMEOUT_SECONDS); + this.expirationPolicy.setTimeToKillInSeconds(SLIDING_TIMEOUT_SECONDS); + this.ticketGrantingTicket = new TicketGrantingTicketImpl("test", TestUtils.getAuthentication(), this.expirationPolicy); + } + + public void testTgtIsExpiredByHardTimeOut() throws InterruptedException { + try { + // 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 - 100); + assertFalse(this.ticketGrantingTicket.isExpired()); + } + + // final sliding window extension past the HARD_TIMEOUT + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT / 2 + 100); + assertTrue(ticketGrantingTicket.isExpired()); + + } catch (InterruptedException e) { + throw e; + } + } + + public void testTgtIsExpiredBySlidingWindow() throws InterruptedException { + try { + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT - 100); + assertFalse(ticketGrantingTicket.isExpired()); + + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT - 100); + assertFalse(ticketGrantingTicket.isExpired()); + + ticketGrantingTicket.grantServiceTicket("test", TestUtils.getService(), expirationPolicy, false); + Thread.sleep(SLIDING_TIMEOUT + 100); + assertTrue(ticketGrantingTicket.isExpired()); + + } catch (InterruptedException e) { + throw e; + } + } + +} 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 0000000..14650d5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/ticket/support/TimeoutExpirationPolicyTests.java @@ -0,0 +1,67 @@ +/* + * 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. + */ +package org.jasig.cas.ticket.support; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.ticket.ExpirationPolicy; +import org.jasig.cas.ticket.Ticket; +import org.jasig.cas.ticket.TicketGrantingTicketImpl; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class TimeoutExpirationPolicyTests extends TestCase { + + private static final long TIMEOUT = 5000; + + private ExpirationPolicy expirationPolicy; + + private Ticket ticket; + + protected void setUp() throws Exception { + this.expirationPolicy = new TimeoutExpirationPolicy(TIMEOUT); + + this.ticket = new TicketGrantingTicketImpl("test", TestUtils + .getAuthentication(), this.expirationPolicy); + + super.setUp(); + } + + public void testTicketIsNull() { + assertTrue(this.expirationPolicy.isExpired(null)); + } + + public void testTicketIsNotExpired() { + assertFalse(this.ticket.isExpired()); + } + + public void testTicketIsExpired() { + try { + Thread.sleep(TIMEOUT + 10); // this failed when it was only +1...not + // accurate?? + assertTrue(this.ticket.isExpired()); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + } +} 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 0000000..9bde7eb --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultLongNumericGeneratorTests.java @@ -0,0 +1,60 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import org.jasig.cas.util.DefaultLongNumericGenerator; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class DefaultLongNumericGeneratorTests extends TestCase { + + public void testWrap() { + assertEquals(Long.MAX_VALUE, new DefaultLongNumericGenerator(Long.MAX_VALUE) + .getNextLong()); + } + + public void testInitialValue() { + assertEquals(10L, new DefaultLongNumericGenerator(10L) + .getNextLong()); + } + + public void testIncrementWithNoWrap() { + assertEquals(0, new DefaultLongNumericGenerator().getNextLong()); + } + + public void testIncrementWithNoWrap2() { + final DefaultLongNumericGenerator g = new DefaultLongNumericGenerator(); + g.getNextLong(); + assertEquals(1, g.getNextLong()); + } + + public void testMinimumSize() { + assertEquals(1, new DefaultLongNumericGenerator().minLength()); + } + + public void testMaximumLength() { + 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 0000000..472c315 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultRandomStringGeneratorTests.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class DefaultRandomStringGeneratorTests extends TestCase { + + private static final int LENGTH = 35; + + private RandomStringGenerator randomStringGenerator = new DefaultRandomStringGenerator( + LENGTH); + + public void testMaxLength() { + assertEquals(LENGTH, this.randomStringGenerator.getMaxLength()); + } + + public void testMinLength() { + assertEquals(LENGTH, this.randomStringGenerator.getMinLength()); + } + + public void testRandomString() { + 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 0000000..b407921 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/DefaultUniqueTicketIdGeneratorTests.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class DefaultUniqueTicketIdGeneratorTests extends TestCase { + + public void testUniqueGenerationOfTicketIds() { + DefaultUniqueTicketIdGenerator generator = new DefaultUniqueTicketIdGenerator( + 10); + + assertNotSame(generator.getNewTicketId("TEST"), generator + .getNewTicketId("TEST")); + } + + public void testSuffix() { + final String SUFFIX = "suffix"; + DefaultUniqueTicketIdGenerator generator = new DefaultUniqueTicketIdGenerator(SUFFIX); + + assertTrue(generator.getNewTicketId("test").endsWith(SUFFIX)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/HttpClientTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/HttpClientTests.java new file mode 100644 index 0000000..02a4950 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/HttpClientTests.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +package org.jasig.cas.util; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class HttpClientTests extends TestCase { + + private HttpClient httpClient; + + protected void setUp() throws Exception { + this.httpClient = new HttpClient(); + this.httpClient.setConnectionTimeout(1000); + this.httpClient.setReadTimeout(1000); + } + + public void testOkayUrl() { + assertTrue(this.httpClient.isValidEndPoint("http://www.jasig.org")); + } + + public void testBadUrl() { + assertFalse(this.httpClient.isValidEndPoint("http://www.jasig.org/scottb.html")); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/util/SamlCompliantUniqueTicketIdGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/util/SamlCompliantUniqueTicketIdGeneratorTests.java new file mode 100644 index 0000000..fb6659d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/util/SamlCompliantUniqueTicketIdGeneratorTests.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +package org.jasig.cas.util; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.4.3 + */ +public final class SamlCompliantUniqueTicketIdGeneratorTests extends TestCase { + + public void testSaml1Compliant() { + final SamlCompliantUniqueTicketIdGenerator g = new SamlCompliantUniqueTicketIdGenerator("http://www.cnn.com"); + assertNotNull(g.getNewTicketId("TT")); + } + + public void testSaml2Compliant() { + final SamlCompliantUniqueTicketIdGenerator g = new SamlCompliantUniqueTicketIdGenerator("http://www.cnn.com"); + g.setSaml2compliant(true); + assertNotNull(g.getNewTicketId("TT")); + + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/validation/AssertionImplTests.java b/cas-server-core/src/test/java/org/jasig/cas/validation/AssertionImplTests.java new file mode 100644 index 0000000..2af9069 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/AssertionImplTests.java @@ -0,0 +1,121 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +import java.util.ArrayList; +import java.util.List; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.principal.Service; +import org.jasig.cas.validation.ImmutableAssertionImpl; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class AssertionImplTests extends TestCase { + + public void testGettersForChainedPrincipals() { + final List list = new ArrayList(); + + list.add(TestUtils.getAuthentication("test")); + list.add(TestUtils.getAuthentication("test1")); + list.add(TestUtils.getAuthentication("test2")); + + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl( + list, TestUtils.getService(), true); + + assertEquals(list.toArray(new Authentication[0]).length, assertion + .getChainedAuthentications().size()); + } + + public void testGetterFalseForNewLogin() { + final List list = new ArrayList(); + + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl( + list, TestUtils.getService(), false); + + assertFalse(assertion.isFromNewLogin()); + } + + public void testGetterTrueForNewLogin() { + final List list = new ArrayList(); + + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl( + list, TestUtils.getService(), true); + + assertTrue(assertion.isFromNewLogin()); + } + + public void testEqualsWithNull() { + final List list = new ArrayList(); + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl( + list, TestUtils.getService(), true); + + assertFalse(assertion.equals(null)); + } + + public void testEqualsWithInvalidObject() { + final List list = new ArrayList(); + list.add(TestUtils.getAuthentication()); + + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl( + list, TestUtils.getService(), true); + + assertFalse(assertion.equals("test")); + } + + public void testEqualsWithValidObject() { + final List list = new ArrayList(); + final List list1 = new ArrayList(); + + final Authentication auth = TestUtils.getAuthentication(); + list.add(auth); + list1.add(auth); + + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl( + list, TestUtils.getService(), true); + final ImmutableAssertionImpl assertion1 = new ImmutableAssertionImpl( + list1, TestUtils.getService(), true); + + assertTrue(assertion.equals(assertion1)); + } + + public void testGetService() { + final Service service = TestUtils.getService(); + + final List list = new ArrayList(); + list.add(TestUtils.getAuthentication()); + + final Assertion assertion = new ImmutableAssertionImpl(list, service, + false); + + assertEquals(service, assertion.getService()); + } +} 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 0000000..2964d4c --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas10ProtocolValidationSpecificationTests.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +import org.jasig.cas.TestUtils; +import org.jasig.cas.validation.Cas10ProtocolValidationSpecification; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class Cas10ProtocolValidationSpecificationTests extends TestCase { + + public void testRenewGettersAndSettersFalse() { + Cas10ProtocolValidationSpecification s = new Cas10ProtocolValidationSpecification(); + s.setRenew(false); + assertFalse(s.isRenew()); + } + + public void testRenewGettersAndSettersTrue() { + Cas10ProtocolValidationSpecification s = new Cas10ProtocolValidationSpecification(); + s.setRenew(true); + assertTrue(s.isRenew()); + } + + public void testRenewAsTrueAsConstructor() { + assertTrue(new Cas10ProtocolValidationSpecification(true).isRenew()); + } + + public void testRenewAsFalseAsConstructor() { + assertFalse(new Cas10ProtocolValidationSpecification(false).isRenew()); + } + + public void testSatisfiesSpecOfTrue() { + assertTrue(new Cas10ProtocolValidationSpecification(true) + .isSatisfiedBy(TestUtils.getAssertion(true))); + } + + public void testNotSatisfiesSpecOfTrue() { + assertFalse(new Cas10ProtocolValidationSpecification(true) + .isSatisfiedBy(TestUtils.getAssertion(false))); + } + + public void testSatisfiesSpecOfFalse() { + assertTrue(new Cas10ProtocolValidationSpecification(false) + .isSatisfiedBy(TestUtils.getAssertion(true))); + } + + public void testSatisfiesSpecOfFalse2() { + 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 0000000..e2b5dee --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20ProtocolValidationSpecificationTests.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +import org.jasig.cas.TestUtils; + +import org.jasig.cas.validation.Cas20ProtocolValidationSpecification; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class Cas20ProtocolValidationSpecificationTests extends TestCase { + + public void testRenewGettersAndSettersFalse() { + Cas20ProtocolValidationSpecification s = new Cas20ProtocolValidationSpecification(); + s.setRenew(false); + assertFalse(s.isRenew()); + } + + public void testRenewGettersAndSettersTrue() { + Cas20ProtocolValidationSpecification s = new Cas20ProtocolValidationSpecification(); + s.setRenew(true); + assertTrue(s.isRenew()); + } + + public void testRenewAsTrueAsConstructor() { + assertTrue(new Cas20ProtocolValidationSpecification(true).isRenew()); + } + + public void testRenewAsFalseAsConstructor() { + assertFalse(new Cas20ProtocolValidationSpecification(false).isRenew()); + } + + public void testSatisfiesSpecOfTrue() { + assertTrue(new Cas20ProtocolValidationSpecification(true) + .isSatisfiedBy(TestUtils.getAssertion(true))); + } + + public void testNotSatisfiesSpecOfTrue() { + assertFalse(new Cas20ProtocolValidationSpecification(true) + .isSatisfiedBy(TestUtils.getAssertion(false))); + } + + public void testSatisfiesSpecOfFalse() { + assertTrue(new Cas20ProtocolValidationSpecification(false) + .isSatisfiedBy(TestUtils.getAssertion(true))); + } + + public void testSatisfiesSpecOfFalse2() { + 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 0000000..eada620 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/validation/Cas20WithoutProxyingValidationSpecificationTests.java @@ -0,0 +1,65 @@ +/* + * 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. + */ +package org.jasig.cas.validation; + +import org.jasig.cas.TestUtils; + +import junit.framework.TestCase; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date: 2007-01-22 15:35:37 -0500 (Mon, 22 Jan + * 2007) $ + * @since 3.0 + */ +public class Cas20WithoutProxyingValidationSpecificationTests extends TestCase { + + private Cas20WithoutProxyingValidationSpecification validationSpecification; + + protected void setUp() throws Exception { + this.validationSpecification = new Cas20WithoutProxyingValidationSpecification(); + } + + public void testSatisfiesSpecOfTrue() { + assertTrue(this.validationSpecification.isSatisfiedBy(TestUtils + .getAssertion(true))); + } + + public void testNotSatisfiesSpecOfTrue() { + this.validationSpecification.setRenew(true); + assertFalse(this.validationSpecification.isSatisfiedBy(TestUtils + .getAssertion(false))); + } + + public void testSatisfiesSpecOfFalse() { + assertTrue(this.validationSpecification.isSatisfiedBy(TestUtils + .getAssertion(false))); + } + + public void testDoesNotSatisfiesSpecOfFalse() { + assertFalse(this.validationSpecification.isSatisfiedBy(TestUtils + .getAssertion(false, new String[] {"test2"}))); + } + + public void testSettingRenew() { + final Cas20WithoutProxyingValidationSpecification validation = new Cas20WithoutProxyingValidationSpecification( + true); + assertTrue(validation.isRenew()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/LogoutControllerTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/LogoutControllerTests.java new file mode 100644 index 0000000..40ca673 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/LogoutControllerTests.java @@ -0,0 +1,125 @@ +/* + * 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. + */ +package org.jasig.cas.web; + +import javax.servlet.http.Cookie; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +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.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.servlet.view.RedirectView; +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class LogoutControllerTests extends AbstractCentralAuthenticationServiceTest { + + private static final String COOKIE_TGC_ID = "CASTGC"; + + private LogoutController logoutController; + + private CookieRetrievingCookieGenerator warnCookieGenerator; + + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + private InMemoryServiceRegistryDaoImpl serviceRegistryDao; + + private DefaultServicesManagerImpl serviceManager; + + private MockHttpServletRequest request; + + @Before + public void onSetUp() throws Exception { + this.request = new MockHttpServletRequest(); + 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.logoutController = new LogoutController(); + this.logoutController.setCentralAuthenticationService(getCentralAuthenticationService()); + this.logoutController.setLogoutView("test"); + this.logoutController.setWarnCookieGenerator(this.warnCookieGenerator); + this.logoutController.setTicketGrantingTicketCookieGenerator(this.ticketGrantingTicketCookieGenerator); + this.logoutController.setServicesManager(this.serviceManager); + } + + @Test + public void testLogoutNoCookie() throws Exception { + assertNotNull(this.logoutController.handleRequestInternal( + this.request, new MockHttpServletResponse())); + } + + @Test + public void testLogoutForServiceWithFollowRedirectsAndMatchingService() throws Exception { + this.request.addParameter("service", "TestService"); + final RegisteredServiceImpl impl = new RegisteredServiceImpl(); + impl.setServiceId("TestService"); + impl.setName("TestService"); + impl.setEnabled(true); + this.serviceManager.save(impl); + this.logoutController.setFollowServiceRedirects(true); + assertTrue(this.logoutController.handleRequestInternal(request, + new MockHttpServletResponse()).getView() instanceof RedirectView); + } + + @Test + public void logoutForServiceWithNoFollowRedirects() throws Exception { + this.request.addParameter("service", "TestService"); + this.logoutController.setFollowServiceRedirects(false); + assertTrue(!(this.logoutController.handleRequestInternal(request, + new MockHttpServletResponse()).getView() instanceof RedirectView)); + } + + @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.logoutController.setFollowServiceRedirects(true); + assertTrue(!(this.logoutController.handleRequestInternal(request, + new MockHttpServletResponse()).getView() instanceof RedirectView)); + } + + @Test + public void testLogoutCookie() throws Exception { + Cookie cookie = new Cookie(COOKIE_TGC_ID, "test"); + this.request.setCookies(new Cookie[] {cookie}); + assertNotNull(this.logoutController.handleRequestInternal(request, + new MockHttpServletResponse())); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/ProxyControllerTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/ProxyControllerTests.java new file mode 100644 index 0000000..9d261c8 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/ProxyControllerTests.java @@ -0,0 +1,103 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class ProxyControllerTests extends AbstractCentralAuthenticationServiceTest { + + private ProxyController proxyController; + + @Before + public void onSetUp() throws Exception { + this.proxyController = new ProxyController(); + this.proxyController + .setCentralAuthenticationService(getCentralAuthenticationService()); + + StaticApplicationContext context = new StaticApplicationContext(); + context.refresh(); + this.proxyController.setApplicationContext(context); + } + + @Test + public void testNoParams() throws Exception { + assertEquals("INVALID_REQUEST", this.proxyController + .handleRequestInternal(new MockHttpServletRequest(), + new MockHttpServletResponse()).getModel() + .get("code")); + } + + @Test + public void testNonExistentPGT() throws Exception { + 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 testExistingPGT() throws Exception { + final TicketGrantingTicket ticket = new TicketGrantingTicketImpl( + "ticketGrantingTicketId", TestUtils.getAuthentication(), + new NeverExpiresExpirationPolicy()); + getTicketRegistry().addTicket(ticket); + 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 testNotAuthorizedPGT() 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-core/src/test/java/org/jasig/cas/web/ServiceValidateControllerTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/ServiceValidateControllerTests.java new file mode 100644 index 0000000..2d7cd8f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/ServiceValidateControllerTests.java @@ -0,0 +1,229 @@ +/* + * 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. + */ +package org.jasig.cas.web; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.mock.MockValidationSpecification; +import org.jasig.cas.ticket.TicketException; +import org.jasig.cas.ticket.proxy.support.Cas10ProxyHandler; +import org.jasig.cas.ticket.proxy.support.Cas20ProxyHandler; +import org.jasig.cas.util.HttpClient; +import org.jasig.cas.validation.Cas20ProtocolValidationSpecification; +import org.jasig.cas.web.support.CasArgumentExtractor; +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 org.springframework.web.servlet.ModelAndView; +import static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class ServiceValidateControllerTests extends AbstractCentralAuthenticationServiceTest { + + private static final String CONST_SUCCESS_VIEW = "casServiceSuccessView"; + + private static final String CONST_FAILURE_VIEW = "casServiceFailureView"; + + private ServiceValidateController serviceValidateController; + + @Before + public void onSetUp() throws Exception { + StaticApplicationContext context = new StaticApplicationContext(); + context.refresh(); + this.serviceValidateController = new ServiceValidateController(); + this.serviceValidateController + .setCentralAuthenticationService(getCentralAuthenticationService()); + final Cas20ProxyHandler proxyHandler = new Cas20ProxyHandler(); + proxyHandler.setHttpClient(new HttpClient()); + this.serviceValidateController.setProxyHandler(proxyHandler); + this.serviceValidateController.setApplicationContext(context); + this.serviceValidateController.setArgumentExtractor(new CasArgumentExtractor()); + } + + private HttpServletRequest getHttpServletRequest() throws TicketException { + final String tId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + getCentralAuthenticationService().grantServiceTicket(tId, + TestUtils.getService()); + final String sId2 = getCentralAuthenticationService() + .grantServiceTicket(tId, TestUtils.getService()); + + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService() + .getId()); + request.addParameter("ticket", sId2); + request.addParameter("renew", "true"); + + return request; + } + + @Test + public void testAfterPropertesSetTestEverything() throws Exception { + this.serviceValidateController + .setValidationSpecificationClass(Cas20ProtocolValidationSpecification.class); + this.serviceValidateController.setSuccessView(CONST_SUCCESS_VIEW); + this.serviceValidateController.setFailureView(CONST_FAILURE_VIEW); + this.serviceValidateController.setProxyHandler(new Cas20ProxyHandler()); + } + + @Test + public void testEmptyParams() throws Exception { + assertNotNull(this.serviceValidateController.handleRequestInternal( + new MockHttpServletRequest(), new MockHttpServletResponse()) + .getModel().get("code")); + } + + @Test + public void testValidServiceTicket() throws Exception { + final String tId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String sId = getCentralAuthenticationService() + .grantServiceTicket(tId, TestUtils.getService()); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService() + .getId()); + request.addParameter("ticket", sId); + + assertEquals(CONST_SUCCESS_VIEW, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void testValidServiceTicketInvalidSpec() throws Exception { + + assertEquals(CONST_FAILURE_VIEW, + this.serviceValidateController.handleRequestInternal( + getHttpServletRequest(), new MockHttpServletResponse()) + .getViewName()); + } + + @Test + public void testValidServiceTicketRuntimeExceptionWithSpec() + throws Exception { + this.serviceValidateController + .setValidationSpecificationClass(MockValidationSpecification.class); + + try { + assertEquals(CONST_FAILURE_VIEW, + this.serviceValidateController.handleRequestInternal( + getHttpServletRequest(), new MockHttpServletResponse()) + .getViewName()); + fail(TestUtils.CONST_EXCEPTION_EXPECTED); + } catch (RuntimeException e) { + // nothing to do here, exception is expected. + } + } + + @Test + public void testInvalidServiceTicket() throws Exception { + final String tId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String sId = getCentralAuthenticationService() + .grantServiceTicket(tId, TestUtils.getService()); + + getCentralAuthenticationService().destroyTicketGrantingTicket(tId); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService() + .getId()); + request.addParameter("ticket", sId); + + assertEquals(CONST_FAILURE_VIEW, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void testValidServiceTicketWithPgt() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final String tId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String sId = getCentralAuthenticationService() + .grantServiceTicket(tId, TestUtils.getService()); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService() + .getId()); + request.addParameter("ticket", sId); + request + .addParameter("pgtUrl", "https://www.acs.rutgers.edu"); + + assertEquals(CONST_SUCCESS_VIEW, + this.serviceValidateController.handleRequestInternal(request, + new MockHttpServletResponse()).getViewName()); + } + + @Test + public void testValidServiceTicketWithBadPgt() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final String tId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String sId = getCentralAuthenticationService() + .grantServiceTicket(tId, TestUtils.getService()); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService() + .getId()); + request.addParameter("ticket", sId); + request.addParameter("pgtUrl", "http://www.acs.rutgers.edu"); + + final ModelAndView modelAndView = this.serviceValidateController + .handleRequestInternal(request, new MockHttpServletResponse()); + assertEquals(CONST_SUCCESS_VIEW, modelAndView + .getViewName()); + assertNull(modelAndView.getModel().get("pgtIou")); + } + + @Test + public void testValidServiceTicketWithInvalidPgt() throws Exception { + this.serviceValidateController.setProxyHandler(new Cas10ProxyHandler()); + final String tId = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final String sId = getCentralAuthenticationService() + .grantServiceTicket(tId, TestUtils.getService()); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter("service", TestUtils.getService() + .getId()); + request.addParameter("ticket", sId); + request.addParameter("pgtUrl", "duh"); + + final ModelAndView modelAndView = this.serviceValidateController + .handleRequestInternal(request, new MockHttpServletResponse()); + assertEquals(CONST_SUCCESS_VIEW, modelAndView + .getViewName()); + assertNull(modelAndView.getModel().get("pgtIou")); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/flow/AuthenticationViaFormActionTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/flow/AuthenticationViaFormActionTests.java new file mode 100644 index 0000000..fd6805b --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/flow/AuthenticationViaFormActionTests.java @@ -0,0 +1,282 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import javax.servlet.http.HttpServletRequest; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.principal.Credentials; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.jasig.cas.web.bind.CredentialsBinder; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.BeanInitializationException; +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.Event; +import org.springframework.webflow.test.MockRequestContext; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.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); + // this.action.afterPropertiesSet(); + } + + @Test + public void testSuccessfulAuthenticationWithNoService() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + request.addParameter("username", "test"); + request.addParameter("password", "test"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + context.getRequestScope().put("credentials", + TestUtils.getCredentialsWithSameUsernameAndPassword()); +// this.action.bind(context); +// assertEquals("success", this.action.submit(context).getId()); + } + + @Test + public void testSuccessfulAuthenticationWithNoServiceAndWarn() + throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockRequestContext context = new MockRequestContext(); + + request.addParameter("username", "test"); + request.addParameter("password", "test"); + request.addParameter("warn", "true"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, response)); + context.getRequestScope().put("credentials", + TestUtils.getCredentialsWithSameUsernameAndPassword()); + // this.action.bind(context); + // assertEquals("success", this.action.submit(context).getId()); +// assertNotNull(response.getCookie(this.warnCookieGenerator +// .getCookieName())); + } + + @Test + public void testSuccessfulAuthenticationWithServiceAndWarn() + throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockRequestContext context = new MockRequestContext(); + + request.addParameter("username", "test"); + request.addParameter("password", "test"); + request.addParameter("warn", "true"); + request.addParameter("service", "test"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, response)); + context.getRequestScope().put("credentials", + TestUtils.getCredentialsWithSameUsernameAndPassword()); + // this.action.bind(context); + // assertEquals("success", this.action.submit(context).getId()); +// assertNotNull(response.getCookie(this.warnCookieGenerator +// .getCookieName())); + } + + @Test + public void testFailedAuthenticationWithNoService() 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())); + + context.getRequestScope().put("credentials", + TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + context.getRequestScope().put( + "org.springframework.validation.BindException.credentials", + new BindException(TestUtils + .getCredentialsWithDifferentUsernameAndPassword(), + "credentials")); + + // this.action.bind(context); +// assertEquals("error", this.action.submit(context).getId()); + } + + @Test + public void testRenewWithServiceAndSameCredentials() throws Exception { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + context.getFlowScope().put("ticketGrantingTicketId", ticketGrantingTicket); + 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("test")); + // this.action.bind(context); + // assertEquals("warn", this.action.submit(context).getId()); + } + + @Test + public void testRenewWithServiceAndDifferentCredentials() throws Exception { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + context.getFlowScope().put("ticketGrantingTicketId", 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())); + // this.action.bind(context); + + // assertEquals("success", this.action.submit(context).getId()); + } + + @Test + public void testRenewWithServiceAndBadCredentials() throws Exception { + final String ticketGrantingTicket = getCentralAuthenticationService() + .createTicketGrantingTicket( + TestUtils.getCredentialsWithSameUsernameAndPassword()); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockRequestContext context = new MockRequestContext(); + + context.getFlowScope().put("ticketGrantingTicketId", ticketGrantingTicket); + request.addParameter("renew", "true"); + request.addParameter("service", "test"); + + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + context.getRequestScope().put("credentials", + TestUtils.getCredentialsWithDifferentUsernameAndPassword()); + context.getRequestScope().put( + "org.springframework.validation.BindException.credentials", + new BindException(TestUtils + .getCredentialsWithDifferentUsernameAndPassword(), + "credentials")); + // this.action.bind(context); + // assertEquals("error", this.action.submit(context).getId()); + } + + @Test + public void testTestBindingWithoutCredentialsBinder() throws Exception { + final MockRequestContext context = new MockRequestContext(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + // context.setLastEvent(new Event(this, "test")); + request.addParameter("username", "test"); + request.addParameter("password", "test"); + + // this.action.bind(context); + // assertEquals("success", this.action.bindAndValidate(context).getId()); + + } + + @Test + public void testTestBindingWithCredentialsBinder() throws Exception { + final MockRequestContext context = new MockRequestContext(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), new MockHttpServletRequest(), + new MockHttpServletResponse())); + // context.setLastEvent(new Event(this, "test")); + + final CredentialsBinder cb = new CredentialsBinder(){ + + public void bind(HttpServletRequest request, Credentials credentials) { + ((UsernamePasswordCredentials) credentials) + .setUsername("test2"); + ((UsernamePasswordCredentials) credentials) + .setPassword("test2"); + } + + public boolean supports(Class clazz) { + return true; + } + + }; + this.action.setCredentialsBinder(cb); + // this.action.bindAndValidate(context); + + // assertEquals( + // "test2", + // ((UsernamePasswordCredentials) context + // .getFlowScope().get( + // "credentials")).getUsername()); + + } + + @Test + public void testSetCredentialsBinderNoFailure() throws Exception { + final CredentialsBinder c = new CredentialsBinder(){ + + public void bind(final HttpServletRequest request, + final Credentials credentials) { + // nothing to do here + } + + public boolean supports(final Class clazz) { + return true; + } + }; + + this.action.setCredentialsBinder(c); + // this.action.afterPropertiesSet(); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/flow/GenerateServiceTicketActionTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/flow/GenerateServiceTicketActionTests.java new file mode 100644 index 0000000..8add161 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/flow/GenerateServiceTicketActionTests.java @@ -0,0 +1,115 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import javax.servlet.http.Cookie; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +import org.jasig.cas.TestUtils; +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 static org.junit.Assert.*; + +/** + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.4 + */ +public final class GenerateServiceTicketActionTests extends AbstractCentralAuthenticationServiceTest { + + private GenerateServiceTicketAction action; + + private String 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 testServiceTicketFromCookie() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + context.getFlowScope().put("ticketGrantingTicketId", this.ticketGrantingTicket); + MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + request.setCookies(new Cookie[] {new Cookie("TGT", + this.ticketGrantingTicket)}); + + this.action.execute(context); + + assertNotNull(WebUtils.getServiceTicketFromRequestScope(context)); + } + + @Test + public void testTicketGrantingTicketFromRequest() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + WebUtils.putTicketGrantingTicketInRequestScope(context, + this.ticketGrantingTicket); + + this.action.execute(context); + + assertNotNull(WebUtils.getServiceTicketFromRequestScope(context)); + } + + @Test + public void testTicketGrantingTicketNoTgt() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + WebUtils.putTicketGrantingTicketInRequestScope(context, "bleh"); + + assertEquals("error", this.action.execute(context).getId()); + } + + @Test + public void testTicketGrantingTicketNotTgtButGateway() throws Exception { + MockRequestContext context = new MockRequestContext(); + context.getFlowScope().put("service", TestUtils.getService()); + MockHttpServletRequest request = new MockHttpServletRequest(); + context.setExternalContext(new ServletExternalContext( + new MockServletContext(), request, new MockHttpServletResponse())); + request.addParameter("service", "service"); + request.addParameter("gateway", "true"); + WebUtils.putTicketGrantingTicketInRequestScope(context, "bleh"); + + assertEquals("gateway", this.action.execute(context).getId()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/flow/InitialFlowSetupActionTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/flow/InitialFlowSetupActionTests.java new file mode 100644 index 0000000..79847b2 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/flow/InitialFlowSetupActionTests.java @@ -0,0 +1,118 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import java.util.Arrays; + +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.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; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.5 + * + */ +public class InitialFlowSetupActionTests extends TestCase { + private final InitialFlowSetupAction action = new InitialFlowSetupAction(); + + private CookieRetrievingCookieGenerator warnCookieGenerator; + + private CookieRetrievingCookieGenerator tgtCookieGenerator; + + protected 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.action.afterPropertiesSet(); + this.action.afterPropertiesSet(); + } + + public void testSettingContextPath() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String CONST_CONTEXT_PATH = "/test"; + 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()); + } + + public void testResettingContexPath() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String CONST_CONTEXT_PATH = "/test"; + final String CONST_CONTEXT_PATH_2 = "/test1"; + 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()); + } + + public void testNoServiceFound() 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()); + } + + public void testServiceFound() 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()); + assertEquals("success", event.getId()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/flow/SendTicketGrantingTicketActionTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/flow/SendTicketGrantingTicketActionTests.java new file mode 100644 index 0000000..6a8a941 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/flow/SendTicketGrantingTicketActionTests.java @@ -0,0 +1,90 @@ +/* + * 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. + */ +package org.jasig.cas.web.flow; + +import javax.servlet.http.Cookie; + +import org.jasig.cas.AbstractCentralAuthenticationServiceTest; +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 static org.junit.Assert.*; + +public class SendTicketGrantingTicketActionTests extends AbstractCentralAuthenticationServiceTest { + private SendTicketGrantingTicketAction action; + + private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; + + private MockRequestContext context; + + @Before + public void onSetUp() throws Exception { + this.action = new SendTicketGrantingTicketAction(); + + this.ticketGrantingTicketCookieGenerator = new CookieRetrievingCookieGenerator(); + + this.ticketGrantingTicketCookieGenerator.setCookieName("TGT"); + + this.action.setCentralAuthenticationService(getCentralAuthenticationService()); + + this.action.setTicketGrantingTicketCookieGenerator(this.ticketGrantingTicketCookieGenerator); + + this.action.afterPropertiesSet(); + + this.context = new MockRequestContext(); + } + + @Test + public void testNoTgtToSet() throws Exception { + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), new MockHttpServletRequest(), new MockHttpServletResponse())); + + assertEquals("success", this.action.execute(this.context).getId()); + } + + @Test + public void testTgtToSet() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + final String TICKET_VALUE = "test"; + + WebUtils.putTicketGrantingTicketInRequestScope(this.context, TICKET_VALUE); + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), new MockHttpServletRequest(), response)); + + assertEquals("success", this.action.execute(this.context).getId()); + assertEquals(TICKET_VALUE, response.getCookies()[0].getValue()); + } + + @Test + public void testTgtToSetRemovingOldTgt() throws Exception { + final MockHttpServletResponse response = new MockHttpServletResponse(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final String TICKET_VALUE = "test"; + request.setCookies(new Cookie[] {new Cookie("TGT", "test5")}); + WebUtils.putTicketGrantingTicketInRequestScope(this.context, TICKET_VALUE); + this.context.setExternalContext(new ServletExternalContext(new MockServletContext(), request, response)); + + assertEquals("success", this.action.execute(this.context).getId()); + assertEquals(TICKET_VALUE, response.getCookies()[0].getValue()); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/init/SafeContextLoaderListenerTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/init/SafeContextLoaderListenerTests.java new file mode 100644 index 0000000..f15af56 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/init/SafeContextLoaderListenerTests.java @@ -0,0 +1,68 @@ +/* + * 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. + */ +package org.jasig.cas.web.init; + +import javax.servlet.ServletContextEvent; + +import org.springframework.mock.web.MockServletContext; +import junit.framework.TestCase; + +/** + * Testcase for SafeContextLoaderListener. + * + * @author Andrew Petro + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class SafeContextLoaderListenerTests extends TestCase { + + private MockServletContext servletContext; + + private ServletContextEvent servletContextEvent; + + private SafeContextLoaderListener listener; + + protected void setUp() throws Exception { + this.listener = new SafeContextLoaderListener(); + this.servletContext = new MockServletContext(); + this.servletContextEvent = new ServletContextEvent(this.servletContext); + } + + /** + * Test that SafeContextLoaderListener does not propogate exceptions thrown + * by its delegate in contextInitialized(). + */ + public void testContextInitialized() { + /* + * this testcase relies upon the fact that ContextLoaderListener() + * throws a FileNotFound exception when invoked in the context of this + * testcase because it does not find the resource + * /WEB-INF/applicationContext.xml if our SafeContextLoaderListener + * instance also throws the exception its delegate threw, this testcase + * will fail. If it catches the exception, this test method will return + * without having failed and so indicate success. + */ + + this.listener.contextInitialized(this.servletContextEvent); + } + + public void testContextDestroy() { + this.listener.contextDestroyed(this.servletContextEvent); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/init/SafeDispatcherServletTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/init/SafeDispatcherServletTests.java new file mode 100644 index 0000000..e210a6d --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/init/SafeDispatcherServletTests.java @@ -0,0 +1,129 @@ +/* + * 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. + */ +package org.jasig.cas.web.init; + +import java.io.IOException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.context.ApplicationContextException; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockServletConfig; +import org.springframework.mock.web.MockServletContext; + +import junit.framework.TestCase; + +/** + * Testcase for SafeDispatcherServlet. + * + * @author Andrew Petro + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class SafeDispatcherServletTests extends TestCase { + + private SafeDispatcherServlet safeServlet; + + private ServletContext mockContext; + + private MockServletConfig mockConfig; + + protected void setUp() throws Exception { + super.setUp(); + + this.safeServlet = new SafeDispatcherServlet(); + + this.mockContext = new MockServletContext(); + this.mockConfig = new MockServletConfig(this.mockContext); + } + + protected void tearDown() throws Exception { + super.tearDown(); + } + + /* + * Test that SafeDispatcherServlet does not propogate exceptions generated + * by its underlying DispatcherServlet on init() and that it stores the + * exception into the ServletContext as the expected attribute name. + */ + public void testInitServletConfig() { + + /* + * we fail if safeServlet propogates exception we rely on the underlying + * DispatcherServlet throwing an exception when init'ed in this way + * without the servlet name having been set and without there being a + * -servlet.xml that it can find on the classpath. + */ + this.safeServlet.init(this.mockConfig); + + /* + * here we test that the particular exception stored by the underlying + * DispatcherServlet has been stored into the ServetContext as an + * attribute as advertised by SafeDispatcherServlet. we rely on knowing + * the particular exception that the underlying DispatcherServlet throws + * under these circumstances; + */ + BeanDefinitionStoreException bdse = (BeanDefinitionStoreException) this.mockContext + .getAttribute(SafeDispatcherServlet.CAUGHT_THROWABLE_KEY); + assertNotNull(bdse); + + } + + /* + * Test that the SafeDispatcherServlet does not service requests when it has + * failed init and instead throws an ApplicationContextException. + */ + public void testService() throws ServletException, IOException { + this.safeServlet.init(this.mockConfig); + + ServletRequest mockRequest = new MockHttpServletRequest(); + ServletResponse mockResponse = new MockHttpServletResponse(); + + try { + this.safeServlet.service(mockRequest, mockResponse); + } catch (ApplicationContextException ace) { + // good, threw the exception we expected. + return; + } + + fail("Should have thrown ApplicationContextException since init() failed."); + } + + public void testServiceSucceeds() { + this.mockConfig = new MockServletConfig(this.mockContext, "cas"); + this.safeServlet.init(this.mockConfig); + + ServletRequest mockRequest = new MockHttpServletRequest(); + ServletResponse mockResponse = new MockHttpServletResponse(); + + try { + this.safeServlet.service(mockRequest, mockResponse); + } catch (ApplicationContextException e) { + System.out.println(e); + fail("Unexpected exception."); + } catch (Exception e) { + return; + } + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests.java new file mode 100644 index 0000000..d922bb0 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests.java @@ -0,0 +1,48 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + */ +public abstract class AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapterTests + extends AbstractThrottledSubmissionHandlerInterceptorAdapterTests { + + 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); + 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-core/src/test/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapterTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapterTests.java new file mode 100644 index 0000000..d60d4c3 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/AbstractThrottledSubmissionHandlerInterceptorAdapterTests.java @@ -0,0 +1,82 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +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 + * @version $Revision$ $Date$ + */ +public abstract class AbstractThrottledSubmissionHandlerInterceptorAdapterTests { + + protected static final int FAILURE_RANGE = 5; + + protected static final int FAILURE_THRESHOLD = 10; + + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + + @Test + public void testThrottle() 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-core/src/test/java/org/jasig/cas/web/support/CookieRetrievingCookieGeneratorTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/CookieRetrievingCookieGeneratorTests.java new file mode 100644 index 0000000..5ae0cd6 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/CookieRetrievingCookieGeneratorTests.java @@ -0,0 +1,86 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import javax.servlet.http.Cookie; + +import org.jasig.cas.authentication.principal.RememberMeCredentials; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.2.1 + * + */ +public final class CookieRetrievingCookieGeneratorTests extends TestCase { + + private CookieRetrievingCookieGenerator g; + + protected void setUp() throws Exception { + this.g = new CookieRetrievingCookieGenerator(); + this.g.setRememberMeMaxAge(100); + this.g.setCookieDomain("cas.org"); + this.g.setCookieMaxAge(5); + this.g.setCookiePath("/"); + this.g.setCookieName("test"); + } + + public void testCookieAddWithRememberMe() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.addParameter(RememberMeCredentials.REQUEST_PARAMETER_REMEMBER_ME, "true"); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + this.g.addCookie(request, response, "test"); + + final Cookie c = response.getCookie("test"); + assertEquals(100, c.getMaxAge()); + assertEquals("test", c.getValue()); + } + + public void testCookieAddWithoutRememberMe() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + + this.g.addCookie(request, response, "test"); + + final Cookie c = response.getCookie("test"); + assertEquals(5, c.getMaxAge()); + assertEquals("test", c.getValue()); + } + + public void testCookieRetrieve() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final Cookie cookie = new Cookie("test", "test"); + cookie.setDomain("cas.org"); + cookie.setMaxAge(5); + request.setCookies(new Cookie[] {cookie}); + + assertEquals("test", this.g.retrieveCookieValue(request)); + + + } + + + +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/support/GoogleAccountsArgumentExtractorTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/GoogleAccountsArgumentExtractorTests.java new file mode 100644 index 0000000..6f876a6 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/GoogleAccountsArgumentExtractorTests.java @@ -0,0 +1,69 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import java.security.PrivateKey; +import java.security.PublicKey; + +import org.jasig.cas.util.PrivateKeyFactoryBean; +import org.jasig.cas.util.PublicKeyFactoryBean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.mock.web.MockHttpServletRequest; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class GoogleAccountsArgumentExtractorTests extends TestCase { + + private GoogleAccountsArgumentExtractor extractor; + + protected 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(); + + this.extractor = new GoogleAccountsArgumentExtractor(); + this.extractor.setPrivateKey((PrivateKey) privKeyFactoryBean.getObject()); + this.extractor.setPublicKey((PublicKey) pubKeyFactoryBean.getObject()); + + super.setUp(); + } + + public void testNoService() { + assertNull(this.extractor.extractService(new MockHttpServletRequest())); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java new file mode 100644 index 0000000..d1fe601 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java @@ -0,0 +1,44 @@ +/* + * 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. + */ +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 + * @version $Revision$ $Date$ + */ +@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-core/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests.java new file mode 100644 index 0000000..54661c5 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapterTests.java @@ -0,0 +1,47 @@ +/* + * 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. + */ +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; + +import static org.junit.Assert.assertEquals; + + +/** + * Unit test for {@link InMemoryThrottledSubmissionByIpAddressHandlerInterceptorAdapter}. + * + * @author Scott Battaglia + * @author Marvin S. Addison + */ +@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-core/src/test/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java new file mode 100644 index 0000000..aeed58e --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests.java @@ -0,0 +1,115 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import com.github.inspektr.common.web.ClientInfo; +import com.github.inspektr.common.web.ClientInfoHolder; +import org.jasig.cas.authentication.AuthenticationManager; +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.junit.Before; +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 + * @version $Revision$ $Date$ + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations={ + "classpath:/applicationContext.xml", + "classpath:/jpaTestApplicationContext.xml", + "classpath:/inspektrThrottledSubmissionContext.xml" +}) +public class InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapterTests + extends AbstractThrottledSubmissionHandlerInterceptorAdapterTests { + + @Autowired + private InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter throttle; + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private DataSource dataSource; + + + @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)"); + } + + + 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); + 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 (AuthenticationException e) { + getThrottle().postHandle(request, response, null, null); + return response; + } + fail("Expected AuthenticationException"); + return null; + } + + private UsernamePasswordCredentials badCredentials(final String username) { + final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(); + credentials.setUsername(username); + credentials.setPassword("badpassword"); + return credentials; + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/support/SamlArgumentExtractorTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/SamlArgumentExtractorTests.java new file mode 100644 index 0000000..fe55c22 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/SamlArgumentExtractorTests.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import org.jasig.cas.authentication.principal.Service; +import org.springframework.mock.web.MockHttpServletRequest; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision: 1.1 $ $Date: 2005/08/19 18:27:17 $ + * @since 3.1 + * + */ +public class SamlArgumentExtractorTests extends TestCase { + + private SamlArgumentExtractor extractor; + + protected void setUp() throws Exception { + this.extractor = new SamlArgumentExtractor(); + super.setUp(); + } + + public void testObtainService() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("TARGET", "test"); + final Service service = this.extractor.extractService(request); + assertEquals("test", service.getId()); + } + + public void testServiceDoesNotExist() { + final MockHttpServletRequest request = new MockHttpServletRequest(); + assertNull(this.extractor.extractService(request)); + } +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/support/WebUtilTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/support/WebUtilTests.java new file mode 100644 index 0000000..5dc350f --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/support/WebUtilTests.java @@ -0,0 +1,77 @@ +/* + * 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. + */ +package org.jasig.cas.web.support; + +import java.util.Arrays; + +import org.jasig.cas.authentication.principal.Service; +import org.springframework.mock.web.MockHttpServletRequest; + +import junit.framework.TestCase; + +/** + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + * + */ +public class WebUtilTests extends TestCase { + + public void testFindService() { + 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()); + } + + public void testFoundNoService() { + 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); + } + /* + * public void testStripJsessionWithoutQueryStringParameters() { + * assertEquals("test", WebUtils.stripJsessionFromUrl("test")); + * assertEquals("http://www.cnn.com", + * WebUtils.stripJsessionFromUrl("http://www.cnn.com;jsession=fsfsadfsdfsafsd")); } + * public void testStripJsessionWithQueryStringParameters() { + * assertEquals("test", WebUtils.stripJsessionFromUrl("test")); + * assertEquals("http://localhost:8080/WebModule2/jsplevel0.jsp?action=test", + * WebUtils.stripJsessionFromUrl("http://localhost:8080/WebModule2/jsplevel0.jsp;jsessionid=CC80B7CC9D62689578A99DB90B187A62?action=test")); } + * public void testStripJsessionWithQueryStringParametersBeforeJsession() { + * assertEquals("test", WebUtils.stripJsessionFromUrl("test")); + * assertEquals("http://localhost:8080/WebModule2/jsplevel0.jsp?action=test", + * WebUtils.stripJsessionFromUrl("http://localhost:8080/WebModule2/jsplevel0.jsp?action=test;jsessionid=CC80B7CC9D62689578A99DB90B187A62")); } + */ +} diff --git a/cas-server-core/src/test/java/org/jasig/cas/web/view/Cas10ResponseViewTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/view/Cas10ResponseViewTests.java new file mode 100644 index 0000000..91e4edd --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/view/Cas10ResponseViewTests.java @@ -0,0 +1,70 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.ImmutableAuthentication; +import org.jasig.cas.authentication.principal.SimplePrincipal; +import org.jasig.cas.validation.ImmutableAssertionImpl; +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 + */ +public class Cas10ResponseViewTests extends TestCase { + + private final Cas10ResponseView view = new Cas10ResponseView(); + + private Map model; + + protected void setUp() throws Exception { + this.model = new HashMap(); + List list = new ArrayList(); + list.add(new ImmutableAuthentication(new SimplePrincipal("test"))); + this.model.put("assertion", new ImmutableAssertionImpl(list, + TestUtils.getService("TestService"), true)); + } + + public void testSuccessView() 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()); + } + + public void testFailureView() 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-core/src/test/java/org/jasig/cas/web/view/Saml10FailureResponseViewTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/view/Saml10FailureResponseViewTests.java new file mode 100644 index 0000000..209d731 --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/view/Saml10FailureResponseViewTests.java @@ -0,0 +1,53 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +import java.util.Collections; + +import junit.framework.TestCase; +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 TestCase { + + private final Saml10FailureResponseView view = new Saml10FailureResponseView(); + + public void testResponse() 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-core/src/test/java/org/jasig/cas/web/view/Saml10SuccessResponseViewTests.java b/cas-server-core/src/test/java/org/jasig/cas/web/view/Saml10SuccessResponseViewTests.java new file mode 100644 index 0000000..b2164cc --- /dev/null +++ b/cas-server-core/src/test/java/org/jasig/cas/web/view/Saml10SuccessResponseViewTests.java @@ -0,0 +1,146 @@ +/* + * 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. + */ +package org.jasig.cas.web.view; + +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 junit.framework.TestCase; +import org.jasig.cas.TestUtils; +import org.jasig.cas.authentication.Authentication; +import org.jasig.cas.authentication.MutableAuthentication; +import org.jasig.cas.authentication.SamlAuthenticationMetaDataPopulator; +import org.jasig.cas.authentication.principal.SimplePrincipal; +import org.jasig.cas.validation.Assertion; +import org.jasig.cas.validation.ImmutableAssertionImpl; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * Unit test for {@link Saml10SuccessResponseView} class. + * + * @author Scott Battaglia + * @author Marvin S. Addison + * @since 3.1 + * + */ +public class Saml10SuccessResponseViewTests extends TestCase { + + private Saml10SuccessResponseView response; + + protected void setUp() throws Exception { + this.response = new Saml10SuccessResponseView(); + this.response.setIssuer("testIssuer"); + this.response.setIssueLength(1000); + super.setUp(); + } + + public void testResponse() 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(new String[] {"tac1", "tac2"})); + final SimplePrincipal principal = new SimplePrincipal("testPrincipal", attributes); + + final MutableAuthentication authentication = new MutableAuthentication(principal); + authentication.getAttributes().put(SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD, SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_SSL_TLS_CLIENT); + authentication.getAttributes().put("testSamlAttribute", "value"); + + final List authentications = new ArrayList(); + authentications.add(authentication); + + final Assertion assertion = new ImmutableAssertionImpl(authentications, 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")); + } + + public void testResponseWithNoAttributes() throws Exception { + final Map model = new HashMap(); + + final SimplePrincipal principal = new SimplePrincipal("testPrincipal"); + + final MutableAuthentication authentication = new MutableAuthentication(principal); + authentication.getAttributes().put(SamlAuthenticationMetaDataPopulator.ATTRIBUTE_AUTHENTICATION_METHOD, SamlAuthenticationMetaDataPopulator.AUTHN_METHOD_SSL_TLS_CLIENT); + authentication.getAttributes().put("testSamlAttribute", "value"); + + final List authentications = new ArrayList(); + authentications.add(authentication); + + final Assertion assertion = new ImmutableAssertionImpl(authentications, 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")); + } + + public void testResponseWithoutAuthMethod() throws Exception { + final Map model = new HashMap(); + + final Map attributes = new HashMap(); + attributes.put("testAttribute", "testValue"); + final SimplePrincipal principal = new SimplePrincipal("testPrincipal", attributes); + + final MutableAuthentication authentication = new MutableAuthentication(principal); + final List authentications = new ArrayList(); + authentications.add(authentication); + + final Assertion assertion = new ImmutableAssertionImpl(authentications, 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("urn:oasis:names:tc:SAML:1.0:am:unspecified")); + } +} diff --git a/cas-server-core/src/test/resources/DSAPrivateKey01.key b/cas-server-core/src/test/resources/DSAPrivateKey01.key new file mode 100644 index 0000000..cecf2ad Binary files /dev/null and b/cas-server-core/src/test/resources/DSAPrivateKey01.key differ diff --git a/cas-server-core/src/test/resources/DSAPublicKey01.key b/cas-server-core/src/test/resources/DSAPublicKey01.key new file mode 100644 index 0000000..74e6ff3 Binary files /dev/null and b/cas-server-core/src/test/resources/DSAPublicKey01.key differ diff --git a/cas-server-core/src/test/resources/META-INF/persistence.xml b/cas-server-core/src/test/resources/META-INF/persistence.xml new file mode 100644 index 0000000..0cee0c9 --- /dev/null +++ b/cas-server-core/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,34 @@ + + + + + org.jasig.cas.services.AbstractRegisteredService + org.jasig.cas.services.RegexRegisteredService + org.jasig.cas.services.RegisteredServiceImpl + org.jasig.cas.ticket.TicketGrantingTicketImpl + org.jasig.cas.ticket.ServiceTicketImpl + org.jasig.cas.ticket.registry.support.JpaLockingStrategy$Lock + + 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 0000000..fef5ab8 --- /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/applicationContext.xml b/cas-server-core/src/test/resources/applicationContext.xml new file mode 100644 index 0000000..df904f7 --- /dev/null +++ b/cas-server-core/src/test/resources/applicationContext.xml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + groupMembership + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/inMemoryThrottledSubmissionContext.xml b/cas-server-core/src/test/resources/inMemoryThrottledSubmissionContext.xml new file mode 100644 index 0000000..91a5c38 --- /dev/null +++ b/cas-server-core/src/test/resources/inMemoryThrottledSubmissionContext.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/inspektrThrottledSubmissionContext.xml b/cas-server-core/src/test/resources/inspektrThrottledSubmissionContext.xml new file mode 100644 index 0000000..b35fe06 --- /dev/null +++ b/cas-server-core/src/test/resources/inspektrThrottledSubmissionContext.xml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/jpaTestApplicationContext.xml b/cas-server-core/src/test/resources/jpaTestApplicationContext.xml new file mode 100644 index 0000000..5224ac6 --- /dev/null +++ b/cas-server-core/src/test/resources/jpaTestApplicationContext.xml @@ -0,0 +1,94 @@ + + + + + + + + + + org.hsqldb.jdbcDriver + sa + + jdbc:hsqldb:mem:cas-ticket-registry + org.hibernate.dialect.HSQLDialect + 1 + + + + + + + + + + ${database.dialect} + update + ${database.batchSize} + + + + + + + + + + + + + + + + + + diff --git a/cas-server-core/src/test/resources/log4j.properties b/cas-server-core/src/test/resources/log4j.properties new file mode 100644 index 0000000..916a06b --- /dev/null +++ b/cas-server-core/src/test/resources/log4j.properties @@ -0,0 +1,33 @@ +# +# log4j configuration to get clean console listing during Maven tests +# + +# +# 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. +# + +log4j.rootCategory=INFO, stdout +log4j.logger.org.apache.xml.security=OFF +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%-5p %d{ISO8601} %t::%c{1} - %m%n + +log4j.logger.org.jasig.cas=DEBUG + +# Log OpenSAML protocol messages +log4j.logger.PROTOCOL_MESSAGE=DEBUG 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 0000000..5fd1ee6 --- /dev/null +++ b/cas-server-core/src/test/resources/org/jasig/cas/authentication/handler/support/jaas.conf @@ -0,0 +1,5 @@ +CAS { +org.jasig.cas.authentication.handler.support.MockLoginModule required;}; + +TEST { +org.jasig.cas.authentication.handler.support.MockLoginModule required;}; diff --git a/cas-server-support-jdbc/pom.xml b/cas-server-support-jdbc/pom.xml new file mode 100644 index 0000000..75e527f --- /dev/null +++ b/cas-server-support-jdbc/pom.xml @@ -0,0 +1,45 @@ + + + + + org.jasig.cas + cas-server + 3.5.2-SNAPSHOT + + 4.0.0 + org.jasig.cas + cas-server-support-jdbc + jar + Jasig CAS JDBC Support + + + + org.jasig.cas + cas-server-core + ${project.version} + + + + org.springframework + spring-jdbc + ${spring.version} + + + 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 0000000..936e32e --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/AbstractJdbcUsernamePasswordAuthenticationHandler.java @@ -0,0 +1,64 @@ +/* + * 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. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler; +import org.springframework.jdbc.core.simple.SimpleJdbcTemplate; + +import javax.sql.DataSource; +import javax.validation.constraints.NotNull; + +/** + * Abstract class for database authentication handlers. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.0.3 + */ +public abstract class AbstractJdbcUsernamePasswordAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler { + + @NotNull + private SimpleJdbcTemplate 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 SimpleJdbcTemplate(dataSource); + this.dataSource = dataSource; + } + + /** + * Method to return the jdbcTemplate + * + * @return a fully created JdbcTemplate. + */ + protected final SimpleJdbcTemplate 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 0000000..d838fcf --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/BindModeSearchDatabaseAuthenticationHandler.java @@ -0,0 +1,57 @@ +/* + * 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. + */ +package org.jasig.cas.adaptors.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.springframework.jdbc.datasource.DataSourceUtils; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class BindModeSearchDatabaseAuthenticationHandler extends + AbstractJdbcUsernamePasswordAuthenticationHandler { + + protected final boolean authenticateUsernamePasswordInternal( + final UsernamePasswordCredentials credentials) + throws AuthenticationException { + final String username = credentials.getUsername(); + final String password = credentials.getPassword(); + + try { + final Connection c = this.getDataSource() + .getConnection(username, password); + DataSourceUtils.releaseConnection(c, this.getDataSource()); + return true; + } catch (final SQLException e) { + return false; + } + } +} 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 0000000..6c7e74e --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/QueryDatabaseAuthenticationHandler.java @@ -0,0 +1,64 @@ +/* + * 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. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.springframework.dao.IncorrectResultSizeDataAccessException; + +import javax.validation.constraints.NotNull; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.0 + */ +public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler { + + @NotNull + private String sql; + + protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { + final String username = getPrincipalNameTransformer().transform(credentials.getUsername()); + final String password = credentials.getPassword(); + final String encryptedPassword = this.getPasswordEncoder().encode( + password); + + try { + final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username); + return dbPassword.equals(encryptedPassword); + } catch (final IncorrectResultSizeDataAccessException e) { + // this means the username was not found. + return false; + } + } + + /** + * @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 0000000..774446c --- /dev/null +++ b/cas-server-support-jdbc/src/main/java/org/jasig/cas/adaptors/jdbc/SearchModeSearchDatabaseAuthenticationHandler.java @@ -0,0 +1,90 @@ +/* + * 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. + */ +package org.jasig.cas.adaptors.jdbc; + +import org.jasig.cas.authentication.handler.AuthenticationException; +import org.jasig.cas.authentication.principal.UsernamePasswordCredentials; +import org.springframework.beans.factory.InitializingBean; + +import javax.validation.constraints.NotNull; + +/** + * 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 + * @version $Revision$ $Date$ + * @since 3.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; + + protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { + final String transformedUsername = getPrincipalNameTransformer().transform(credentials.getUsername()); + final String encyptedPassword = getPasswordEncoder().encode(credentials.getPassword()); + + final int count = getJdbcTemplate().queryForInt(this.sql, + transformedUsername, encyptedPassword); + + return count > 0; + } + + 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-webapp/pom.xml b/cas-server-webapp/pom.xml new file mode 100644 index 0000000..8751268 --- /dev/null +++ b/cas-server-webapp/pom.xml @@ -0,0 +1,277 @@ + + + + + org.jasig.cas + cas-server + 3.5.2-SNAPSHOT + + 4.0.0 + org.jasig.cas + cas-server-webapp + war + Jasig CAS Web Application + + + + + org.jasig.cas + cas-server-support-jdbc + 3.5.2-SNAPSHOT + jar + + + mysql + mysql-connector-java + 5.1.6 + + + com.alibaba + druid + 1.1.2 + + + org.mockito + mockito-all + ${mockito.version} + test + jar + + + com.github.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.jasig.cas.client + cas-client-core + 3.2.1 + + + javax.servlet + servlet-api + + + + + + org.springframework + spring-context-support + compile + + + + org.springframework + spring-expression + ${spring.version} + compile + + + + org.opensymphony.quartz + quartz + 1.6.1 + jar + + + + net.sf.spring-json + spring-json + 1.3.1 + runtime + + + net.sf.sojo + sojo-optional + + + org.springframework + spring + + + org.springframework + spring-mock + + + org.springframework + spring-webmvc + + + cglib + cglib-full + + + + + cglib + cglib-nodep + 2.2.2 + runtime + + + + 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 + + + + ognl + ognl + 2.7.3 + runtime + + + + org.hibernate + hibernate-validator + runtime + + + + + + + org.apache.maven.plugins + maven-war-plugin + + cas + + + ${basedir}/src/main/webapp/WEB-INF + true + WEB-INF + + **/web.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.jasig.maven + maven-translate-plugin + [0.0.1,) + + check + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/java/org/jasig/cas/util/AutowiringSchedulerFactoryBean.java b/cas-server-webapp/src/main/java/org/jasig/cas/util/AutowiringSchedulerFactoryBean.java new file mode 100644 index 0000000..be114d2 --- /dev/null +++ b/cas-server-webapp/src/main/java/org/jasig/cas/util/AutowiringSchedulerFactoryBean.java @@ -0,0 +1,64 @@ +/* + * 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. + */ + +package org.jasig.cas.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContextAware; +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 + * @version $Revision$ $Date$ + * @since 3.3.4 + **/ +public final class AutowiringSchedulerFactoryBean extends SchedulerFactoryBean implements ApplicationContextAware, InitializingBean { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private ApplicationContext applicationContext; + + public void afterPropertiesSet() throws Exception { + final Map triggers = this.applicationContext.getBeansOfType(Trigger.class); + super.setTriggers(triggers.values().toArray(new Trigger[triggers.size()])); + + if (log.isDebugEnabled()) { + log.debug("Autowired the following triggers defined in application context: " + triggers.keySet().toString()); + } + + super.afterPropertiesSet(); + } + + public void setApplicationContext(final ApplicationContext applicationContext) { + super.setApplicationContext(applicationContext); + this.applicationContext = applicationContext; + } +} diff --git a/cas-server-webapp/src/main/java/org/jasig/cas/web/FlowExecutionExceptionResolver.java b/cas-server-webapp/src/main/java/org/jasig/cas/web/FlowExecutionExceptionResolver.java new file mode 100644 index 0000000..67fb89f --- /dev/null +++ b/cas-server-webapp/src/main/java/org/jasig/cas/web/FlowExecutionExceptionResolver.java @@ -0,0 +1,91 @@ +/* + * 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. + */ +package org.jasig.cas.web; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; + +import org.apache.commons.lang.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; + +/** + * 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 + */ +public final class FlowExecutionExceptionResolver implements HandlerExceptionResolver { + + /** Instance of a log. */ + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + @NotNull + private String modelKey = "exception.message"; + + 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) { + log.debug("Ignoring the received exception due to a type mismatch", exception); + return null; + } + + final String urlToRedirectTo = request.getRequestURI() + + (request.getQueryString() != null ? "?" + + request.getQueryString() : ""); + + log.debug("Error getting flow information for URL [{}]", urlToRedirectTo, exception); + final Map model = new HashMap(); + model.put(this.modelKey, StringEscapeUtils.escapeHtml(exception.getMessage())); + + return new ModelAndView(new RedirectView(urlToRedirectTo), model); + } + + public void setModelKey(final String modelKey) { + this.modelKey = modelKey; + } +} diff --git a/cas-server-webapp/src/main/java/org/jasig/cas/web/flow/GatewayServicesManagementCheck.java b/cas-server-webapp/src/main/java/org/jasig/cas/web/flow/GatewayServicesManagementCheck.java new file mode 100644 index 0000000..2daceeb --- /dev/null +++ b/cas-server-webapp/src/main/java/org/jasig/cas/web/flow/GatewayServicesManagementCheck.java @@ -0,0 +1,61 @@ +/* + * 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. + */ + +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.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.validation.constraints.NotNull; + +/** + * Performs an authorization check for the gateway request if there is no Ticket Granting Ticket. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.4.5 + */ +public class GatewayServicesManagementCheck extends AbstractAction { + + @NotNull + private final ServicesManager servicesManager; + + 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(); + } + + throw new UnauthorizedServiceException(String.format("Service [%s] is not authorized to use CAS.", service.getId())); + } +} diff --git a/cas-server-webapp/src/main/java/org/jasig/cas/web/flow/ServiceAuthorizationCheck.java b/cas-server-webapp/src/main/java/org/jasig/cas/web/flow/ServiceAuthorizationCheck.java new file mode 100644 index 0000000..3308c31 --- /dev/null +++ b/cas-server-webapp/src/main/java/org/jasig/cas/web/flow/ServiceAuthorizationCheck.java @@ -0,0 +1,73 @@ +/* + * 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. + */ +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 org.springframework.webflow.test.MockRequestContext; + +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()); + + 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(); + } + final RegisteredService registeredService = this.servicesManager.findServiceBy(service); + + if (registeredService == null) { + logger.warn("Unauthorized Service Access for Service: [ {} ] - service is not defined in the service registry.", service.getId()); + throw new UnauthorizedServiceException(); + } + else if (!registeredService.isEnabled()) { + logger.warn("Unauthorized Service Access for Service: [ {} ] - service is not enabled in the service registry.", service.getId()); + throw new UnauthorizedServiceException(); + } + + return success(); + } +} diff --git a/cas-server-webapp/src/main/resources/META-INF/persistence.xml b/cas-server-webapp/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000..e06479e --- /dev/null +++ b/cas-server-webapp/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,35 @@ + + + + + + org.jasig.cas.services.AbstractRegisteredService + org.jasig.cas.services.RegexRegisteredService + org.jasig.cas.services.RegisteredServiceImpl + org.jasig.cas.ticket.TicketGrantingTicketImpl + org.jasig.cas.ticket.ServiceTicketImpl + org.jasig.cas.ticket.registry.support.JpaLockingStrategy$Lock + + 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 0000000..100d270 --- /dev/null +++ b/cas-server-webapp/src/main/resources/cas-theme-default.properties @@ -0,0 +1,21 @@ +# +# 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. +# + +mobile.custom.css.file=/css/default-mobile-custom.css +standard.custom.css.file=/css/cas.css diff --git a/cas-server-webapp/src/main/resources/default_views.properties b/cas-server-webapp/src/main/resources/default_views.properties new file mode 100644 index 0000000..90dfa94 --- /dev/null +++ b/cas-server-webapp/src/main/resources/default_views.properties @@ -0,0 +1,90 @@ +### Login view (/login) + +# +# 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. +# + +casLoginView.(class)=org.springframework.web.servlet.view.JstlView +casLoginView.url=/WEB-INF/view/jsp/default/ui/casLoginView.jsp + +### Login confirmation view (logged in, warn=true) +casLoginConfirmView.(class)=org.springframework.web.servlet.view.JstlView +casLoginConfirmView.url=/WEB-INF/view/jsp/default/ui/casConfirmView.jsp + +### Logged-in view (logged in, no service provided) +casLoginGenericSuccessView.(class)=org.springframework.web.servlet.view.JstlView +casLoginGenericSuccessView.url=/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp + +### Logout view (/logout) +casLogoutView.(class)=org.springframework.web.servlet.view.JstlView +casLogoutView.url=/WEB-INF/view/jsp/default/ui/casLogoutView.jsp + +### CAS error view +viewServiceErrorView.(class)=org.springframework.web.servlet.view.JstlView +viewServiceErrorView.url=/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp + +viewServiceSsoErrorView.(class)=org.springframework.web.servlet.view.JstlView +viewServiceSsoErrorView.url=/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp + +### Services Management Views +addServiceView.(class)=org.springframework.web.servlet.view.JstlView +addServiceView.url=/WEB-INF/view/jsp/services/add.jsp + +editServiceView.(class)=org.springframework.web.servlet.view.JstlView +editServiceView.url=/WEB-INF/view/jsp/services/add.jsp + +manageServiceView.(class)=org.springframework.web.servlet.view.JstlView +manageServiceView.url=/WEB-INF/view/jsp/services/manage.jsp + +serviceLogoutView.(class)=org.springframework.web.servlet.view.JstlView +serviceLogoutView.url=/WEB-INF/view/jsp/services/logout.jsp + +viewStatisticsView.(class)=org.springframework.web.servlet.view.JstlView +viewStatisticsView.url=/WEB-INF/view/jsp/services/viewStatistics.jsp + +403.(class)=org.springframework.web.servlet.view.JstlView +403.url=/WEB-INF/view/jsp/authorizationFailure.jsp + +### Expired Password Error message +casExpiredPassView.(class)=org.springframework.web.servlet.view.JstlView +casExpiredPassView.url=/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp + +### Locked Account Error message +casAccountLockedView.(class)=org.springframework.web.servlet.view.JstlView +casAccountLockedView.url=/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp + +### Disabled Account Error message +casAccountDisabledView.(class)=org.springframework.web.servlet.view.JstlView +casAccountDisabledView.url=/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp + +### Must Change Password Error message +casMustChangePassView.(class)=org.springframework.web.servlet.view.JstlView +casMustChangePassView.url=/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp + +### Bad Hours Error message +casBadHoursView.(class)=org.springframework.web.servlet.view.JstlView +casBadHoursView.url=/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp + +### Bad Workstation Error message +casBadWorkstationView.(class)=org.springframework.web.servlet.view.JstlView +casBadWorkstationView.url=/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp + +### Password Expiration Warning message (logged in, PasswordWarningCheck=true) +casWarnPassView.(class)=org.springframework.web.servlet.view.JstlView +casWarnPassView.url=/WEB-INF/view/jsp/default/ui/casWarnPassView.jsp + diff --git a/cas-server-webapp/src/main/resources/log4j.xml b/cas-server-webapp/src/main/resources/log4j.xml new file mode 100644 index 0000000..4c6344a --- /dev/null +++ b/cas-server-webapp/src/main/resources/log4j.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..ed81c46 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ar.properties @@ -0,0 +1,116 @@ +#Welcome Screen Messages + +# +# 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. +# + +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 + +# 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{ + + +# 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 +viewStatisticsView=\u0639\u0631\u0636 \u0627\u0644\u0625\u062D\u0635\u0627\u0621\u0627\u062A + +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 + +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=Jasig 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.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.allowedToProxy=\u0627\u0644\u0633\u0645\u0627\u062D \u0627\u0644\u0648\u0643\u064A\u0644 +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.allowedToProxy=\u0627\u0644\u0633\u0645\u0627\u062D \u0627\u0644\u0648\u0643\u064A\u0644 +management.services.manage.label.ssoParticipant=SSO \u0645\u0634\u0627\u0631\u06A9 +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 + +management.services.service.warn=CAS \u0641\u064A \u0642\u064A\u062F \u0627\u0644\u062A\u0634\u063A\u064A\u0644 \u062D\u0627\u0644\u064A\u0627 \u0644\u0627\u0646\u0647 \u0644\u0645 \u064A\u062A\u0645 \u062A\u0643\u0648\u0646 \u0627\u0644\u0646\u0648\u0639 \u0645\u0646 \u0627\u0644\u062E\u062F\u0645\u0627\u062A \u062F\u0627\u062E\u0644 \u0647\u0630\u0647 \u0627\u0644\u0627\u062F\u0627\u0629. \u0628\u0645\u062C\u0631\u062F \u062A\u0643\u0648\u0646 \u0647\u0630\u0647 \u0627\u0644\u0627\u062F\u0627\u0629 \u0644\u062A\u0642\u062F\u064A\u0645 \u0627\u0644\u062E\u062F\u0645\u0629\u060C CAS \u0644\u0645 \u064A\u0639\u062F \u062A\u0639\u062A\u0628\u0631 \u0645\u0641\u062A\u0648\u062D\u0629 \u0648 \u0628\u0627\u0644\u062A\u0627\u0644\u064A \u0627\u064A \u062A\u0637\u0628\u064A\u0642 \u0627\u0644\u0630\u064A \u062A\u0631\u063A\u0628 \u0627\u0633\u062A\u062E\u062F\u0627\u0645\u0647 \u0641\u064A CAS \u064A\u062C\u0628 \u062A\u0633\u062C\u064A\u0644\u0647 \u0641\u064A \u0647\u0630\u0647 \u0627\u0644\u0627\u062F\u0627\u0629. \u0630\u0644\u0643 \u062A\u062A\u0636\u0645\u0646 \u0647\u0630\u0647 \u0627\u0644\u0627\u062F\u0627\u0629 \u060C \u0627\u0648\u0644 \u062E\u062F\u0645\u0629 \u0627\u0644\u062A\u064A \u064A\u062C\u0628 \u0627\u0636\u0627\u0641\u062A\u0647\u0627 \u0647\u064A \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 \u0646\u0641\u0633\u0647\u0627. \u062E\u062F\u0645\u0629 \u0625\u062F\u0627\u0631\u0629 URL \u0647\u0648 {0} 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 0000000..e6eef2a --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ca.properties @@ -0,0 +1,107 @@ +#Author: Evili del Rio i Silvan + +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=Felicitats per engegar CAS correctament! El controlador d'autenticaci\u00db per defecte autentica sempre que el nom d'usuari sigui igual a la contrasenya: som-hi, proveu-lo. +screen.welcome.security=Per raons de seguretat, si us plau, tanqueu la sessi\u00db i el vostre navegador web quan hagi acabat d'accedir als serveis que requereixen autenticaci\u00db. +screen.welcome.instructions=Introdui el vostre Jasig NetID i contrasenya. +screen.welcome.label.netid=NetID: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=Contrasenya: +screen.welcome.label.password.accesskey=c +screen.welcome.label.warn=Aviseu-me abans d'obrir sessi\u00db en altres llocs. +screen.welcome.label.warn.accesskey=a +screen.welcome.button.login=INICIAR SESSI\u201d +screen.welcome.button.clear=NETEJAR + +#Confirmation Screen Messages +screen.confirmation.message=Feu clic aqu\u00cc per anar a l'aplicaci\u00db. + +#Generic Success Screen Messages +screen.success.header=Inici de sessi\u00db satisfactori. +screen.success.success=Heu iniciat sessi\u00db satisfactoriament al Servei Central d'Autenticaci\u00db (CAS). +screen.success.security=Per raons de seguretat, si us plau, tanqueu la sessi\u00db i el vostre navegador web quan hagi acabat d'accedir als serveis que requereixen autenticaci\u00db. + +#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). +screen.logout.security=Per raons de seguretat, tanqueu el vostre navegador web. +screen.logout.redirect=El servei des del qual heu arribat ha proporcionat unenlla\u00c1 al que podeu accedir-ho fent clic aqu\u00cc. +error.invalid.loginticket=No podeu intentar enviar un formulari que ja ha estat enviat. +required.username=El nom d'usuari \u00c8s un camp obligatori. +required.password=La contrasenya \u00c8s un camp obligatori. +error.authentication.credentials.bad=No es pot determinar que les credencials proporcionades siguin aut\u00cbntiques. +error.authentication.credentials.unsupported=Les credencials proporcionades no estan soportadas per CAS. + +INVALID_REQUEST_PROXY=els par\u2021metres 'pgt' i 'targetService' son obligatorios. + +INVALID_TICKET_SPEC=El ticket fall\u00db la especificaci\u00dbn de validaci\u00dbn. Los errores posibles incluyen validar un ticket de proxy (Proxy Ticket) utilizando un validador de tickets de servicio (Service Ticket), o no cumplir con la petici\u00dbn de renovaci\u00dbn (renew true). +INVALID_REQUEST=los par\u2021metros 'service' i 'ticket' s\u00dbn obligatoris + +INVALID_TICKET=no s'ha reconegut el ticket ''{0}'' +INVALID_SERVICE=el ticket ''{0}'' no coincideix amb el servei proporcionat + + +# SERVICES MANAGEMENT + +addServiceView=Afegir un Servei Nou +editServiceView=Editar el Servei +manageServiceView=Administrar els Serveis + +screen.service.error.header=L'aplicaci\u00db no est\u2021 autoritzada per a fer servir CAS +screen.service.error.message=L'aplicaci\u00db que intenta autenticar no est\u2021 autoritzada per CAS. + +registeredService.serviceId.exists=Un Servei amb la mateixa URL de servei ja existeix. + +application.title=Jasig 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.allowedToProxy=Es permet fer proxy +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.allowedToProxy=Proxy perm\u00c8s +management.services.manage.label.ssoParticipant=Participant SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=esborrar 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 0000000..ab8a186 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_cs.properties @@ -0,0 +1,63 @@ +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=Toto je p\u0159ihla\u0161ovac\u00ed str\u00e1nka Centr\u00e1ln\u00ed autentika\u010dn\u00ed slu\u017eby (CAS). +screen.welcome.security=Z bezpe\u010dnostn\u00edch d\u016fvod\u016f se po ukon\u010den\u00ed pr\u00e1ce odhla\u0161te a zav\u0159ete okno prohl\u00ed\u017ee\u010de ! +screen.welcome.instructions=Zadejte sv\u00e9 identifika\u010dn\u00ed \u010d\u00edslo z pr\u016fkazu a sv\u00e9 heslo. +screen.welcome.label.netid=Identifika\u010dn\u00ed \u010d\u00edslo: +screen.welcome.label.netid.accesskey=i +screen.welcome.label.password=Heslo: +screen.welcome.label.password.accesskey=h +screen.welcome.label.warn=Upozornit p\u0159ed p\u0159ihl\u00e1\u0161en\u00ed k j\u00edn\u00e9 aplikaci. +screen.welcome.label.warn.accesskey=u +screen.welcome.button.login=P\u0158IHL\u00c1\u0160EN\u00cd +screen.welcome.button.clear=VYMAZAT + +#Confirmation Screen Messages +screen.confirmation.message=Klikn\u011bte zde pro p\u0159echod na web. + +#Generic Success Screen Messages +screen.success.header=\u00dasp\u011b\u0161n\u00e9 p\u0159ihl\u00e1\u0161en\u00ed +screen.success.success=\u00dasp\u011b\u0161n\u011b jste se p\u0159ihl\u00e1sili k Centr\u00e1ln\u00ed Autentika\u010dn\u00ed Slu\u017eb\u011b. +screen.success.security=Z bezpe\u010dnostn\u00edch d\u016fvod\u016f se po ukon\u010den\u00ed pr\u00e1ce odhla\u0161te a zav\u0159ete okno prohl\u00ed\u017ee\u010de ! + +#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 Centr\u00e1ln\u00ed Autentika\u010dn\u00ed Slu\u017eby. +screen.logout.security=z bezpe\u010dnostn\u00edch d\u016fvod\u016f uzav\u0159ete nyn\u00ed okno prohl\u00ed\u017ee\u010de. +screen.logout.redirect=Web ze kter\u00e9ho jste sem p\u0159i\u0161li doporu\u010dil pokra\u010dovat sem. + +#Service Error Messages +screen.service.error.header=Neautorizovan\u00e1 slu\u017eba +screen.service.error.message=Web ke kter\u00e9mu se sna\u017e\u00edte p\u0159ipojit nen\u00ed opr\u00e1vn\u011bn k vyu\u017eit\u00ed Centr\u00e1ln\u00ed Autentika\u010dn\u00ed Slu\u017eby. + + +error.invalid.loginticket=Nen\u00ed mo\u017en\u00e9 znovu odeslat formul\u00e1\u0159, kter\u00fd u\u017e byl odesl\u00e1n. +required.username=Identifika\u010dn\u00ed \u010d\u00edslo je povinn\u00fd \u00fadaj. +required.password=Heslo je povinn\u00fd \u00fadaj. +error.authentication.credentials.bad=Zadan\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje nejsou validn\u00ed. +error.authentication.credentials.unsupported=Zadan\u00e9 p\u0159ihla\u0161ovac\u00ed \u00fadaje nebylo mo\u017en\u00e9 zpracovat. + +INVALID_REQUEST_PROXY=Parametry 'pgt' a 'targetService' jsou povinn\u00e9 +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=Parametry 'service' a 'ticket' jsou povinn\u00e9 +INVALID_TICKET=ticket ''{0}'' nebyl rozpozn\u00e1n +INVALID_SERVICE=ticket ''{0}'' nesouhlas\u00ed s poskytovanou slu\u017ebou 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 0000000..af7d32a --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_de.properties @@ -0,0 +1,150 @@ +#Author: Konrad Wulf +# businessMart AG +# http://www.businessmart.de + +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=Glckwunsch, Sie haben Ihr CAS System zum Laufen gebracht! Der voreingestellte Authentication Handler gewhrt Einlass, wenn der Benutzername dem Passwort entspricht: Nur zu, probieren Sie es aus. +screen.welcome.security=Aus Sicherheitsgrnden sollten Sie bei Verlassen der passwortgeschtzten Bereiche sich explizit ausloggen und Ihren Webbrowser schlieen! +screen.welcome.instructions=Bitte geben Sie Ihre Jasig 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 mchte 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=LSCHEN + +# Blocked Errors Page +screen.blocked.header=Zugriff verweigert +screen.blocked.message=Das Passwort wurde fr den User zu oft falsch eingegeben. Der Zugriff wird gedrosselt. + +#Confirmation Screen Messages +screen.confirmation.message=Klicken Sie hier um zu der zuvor angeforderten Seite zurckzukehren. + +#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 Sicherheitsgrnden sollten Sie bei Verlassen der passwortgeschtzten 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 Sicherheitsgrnden sollten Sie den Browser schliessen. +screen.logout.redirect=Der Service, von dem Sie herkommen, hat einen Link angegeben, den Sie verfolgen knnen, indem Sie hier klicken. + +#Service Error Messages +screen.service.sso.error.header=Eine Neuanmeldung ist erforderlich, um auf den Service zuzugreifen. +screen.service.sso.error.message=Der Service, fr den Sie versucht haben, sich zu authentifizieren, hat nicht das Recht, CAS zu benutzen. + + +error.invalid.loginticket=Sie knnen kein Formular erneut abschicken, das bereits bertragen wurde. +required.username=Benutzername ist ein Pflichtfeld. +required.password=Passwort ist ein Pflichtfeld. +error.authentication.credentials.bad=Die von Ihnen angegebenen Anmeldedaten wurden nicht akzeptiert. +error.authentication.credentials.unsupported=CAS untersttzt das gewhlte Anmeldeverfahren nicht. + +INVALID_REQUEST_PROXY='pgt' und 'targetService' Parameter werden beide bentigt +INVALID_TICKET_SPEC=Das Ticket entspricht nicht den berprfungsregeln. Ein mglicher Fehler knnte sein, dass versucht wurde, ein Proxy Ticket mit einem Service Ticket Validierer zu berprfen, oder man sich nicht an den renew true request gehalten hat. +INVALID_REQUEST='service' und 'ticket' Parameter werden beide bentigt +INVALID_TICKET=Ticket ''{0}'' wurde nicht anerkannt +INVALID_SERVICE=Ticket ''{0}'' passt nicht zum angegebenen Service. Der ursprngliche Service war ''{1}'' und der bermittelte Service war ''{2}''. + +# SERVICES MANAGEMENT +addServiceView=Neuen Service hinzufgen +editServiceView=Service ndern +manageServiceView=Services Verwalten +viewStatisticsView=Statistik ansehen + +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. + +registeredService.serviceId.exists=Ein Service mit derselben Service Url existiert bereits + +application.title=Jasig 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 gelscht werden +management.services.status.deleted={0} wurde erfolgreich gelscht. + +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 knnen 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.allowedToProxy=Proxy Berechtigung +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 Weboberflche 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.allowedToProxy=Proxy Berechtigung +management.services.manage.label.ssoParticipant=SSO Teilnehmer + +management.services.manage.action.edit=ndern +management.services.manage.action.delete=Lschen + +management.services.service.warn=CAS luft aktuell im "offenen Modus", da keine Services mit diesem Service Verwaltungs Werkzeug konfiguriert wurden. Sobald sie in dieser Weboberflche einen Service hinzufgen, ist CAS nicht mehr "offen" und jede Applikation muss fr die Benutzung berechtigt sein. Die beinhaltet auch diese Service Verwaltungs Werkzeug. Wenn das Werkzeug benutzt wird, muss sie selber als erster Service hinzugefgt werden. Die Standard Service Verwaltungs Werkzeug URL ist "{0}". + +# LPPE Account Error +screen.accounterror.password.message=Passwort nderungsdatum ist nicht definiert, ist abgelaufen oder ungltig. Bitte kontaktieren Sie Ihren System Administrator um wieder Zugriff zu erhalten. + +# LPPE Account Disabled +screen.accountdisabled.heading=Dieses Konto wurde deaktiviert. +screen.accountdisabled.message=Bitte kontaktieren Sie Ihren System Administrator um wieder Zugriff zu erhalten. + +# LPPE Password Expired +screen.expiredpass.heading=Ihr Kennwort ist abgelaufen. +screen.expiredpass.message=Bitte ndern Sie Ihr Kennwort. + +# LPPE Password Must be changed +screen.mustchangepass.heading=Sie mssen Ihr Kennwort ndern. +screen.mustchangepass.message=Bitte ndern Sie Ihr Kennwort. + +# LPPE Login out of authorized hours +screen.badhours.heading=Anmeldung derzeit nicht mglich. +screen.badhours.message=Bitte versuchen Sie es spter noch einmal. + +# LPPE Login out of authorized workstations +screen.badworkstation.heading=Sie knnen sich von dieser Workstation aus nicht anmelden. +screen.badworkstation.message=Bitte kontaktieren Sie Ihren System Administrator um Zugriff zu erhalten. + +# LPPE Password Warning +screen.warnpass.heading.today=Ihr Kennwort luft heute ab! +screen.warnpass.heading.tomorrow=Ihr Kennwort luft morgen ab! +screen.warnpass.heading.other=Ihr Kennwort luft in {0} Tagen ab. +screen.warnpass.message.line1=Bitte ndern Sie Ihr Kennwort. +screen.warnpass.message.line2=Sie werden in 10 Sekunden automatisch zu Ihrer Applikation weitergeleitet. diff --git a/cas-server-webapp/src/main/resources/messages_en.properties b/cas-server-webapp/src/main/resources/messages_en.properties new file mode 100644 index 0000000..08157bd --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_en.properties @@ -0,0 +1,160 @@ +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=Congratulations on bringing CAS online! The default authentication handler authenticates where usernames equal passwords: go ahead, try it out. +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.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 + +# 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. + +#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 have successfully logged into the Central Authentication Service. +screen.success.security=For security reasons, please Log Out and Exit your web browser when you are done accessing services that require authentication! + +#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. + +error.invalid.loginticket=You cannot attempt to re-submit a form that has been submitted already. +required.username=Username is a required field. +required.password=Password is a required field. +error.authentication.credentials.bad=The credentials you provided cannot be determined to be authentic. +error.authentication.credentials.unsupported=The credentials you provided are not supported by CAS. + +INVALID_REQUEST_PROXY='pgt' and 'targetService' parameters are both required +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}''. +UNAUTHORIZED_SERVICE_PROXY=The supplied service ''{0}'' is not authorized to use CAS + + +# SERVICES MANAGEMENT +addServiceView=Add New Service +editServiceView=Edit Service +manageServiceView=Manage Services +viewStatisticsView=View Statistics + +screen.service.error.header=Application Not Authorized to Use CAS +screen.service.error.message=The application you attempted to authenticate to is not authorized to use CAS. + +registeredService.serviceId.exists=A Service with that Service URL already exists. + +application.title=Jasig 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.allowedToProxy=Allowed to proxy +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.ignoreAttributes=Ignore Attribute Management via this Tool +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.allowedToProxy=Can Proxy +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 is currently running in "open mode" because no services are configured within this tool. Once you configure this tool to have a service, CAS is no longer considered open and thus 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}". + +# LPPE Account Error +screen.accounterror.password.message=Password change date is not specified, has expired or is considered invalid. Please contact the system administrator to regain access. + +# LPPE Account Disabled +screen.accountdisabled.heading=This account has been disabled. +screen.accountdisabled.message=Please contact the system administrator to regain access. + +# LPPE Account Locked +screen.accountlocked.heading=This account has been locked. +screen.accountlocked.message=Please contact the system administrator to regain access. + +# LPPE Password Expired +screen.expiredpass.heading=Your password has expired. +screen.expiredpass.message=Please change your password. + +# LPPE Password Must be changed +screen.mustchangepass.heading=You must change your password. +screen.mustchangepass.message=Please change your password. + +# LPPE Login out of authorized hours +screen.badhours.heading=You cannot login at this time. +screen.badhours.message=Please try again later. + +# LPPE Login out of authorized workstations +screen.badworkstation.heading=You cannot login from this workstation. +screen.badworkstation.message=Please contact the system administrator to regain access. + +# LPPE Password Warning +screen.warnpass.heading.today=Your password expires today! +screen.warnpass.heading.tomorrow=Your password expires tomorrow! +screen.warnpass.heading.other=Your password expires in {0} days. +screen.warnpass.message.line1=Please change your password now. +screen.warnpass.message.line2=You will be redirected to your application automatically in 10 seconds. + +# 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 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 0000000..0d4c20a --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_es.properties @@ -0,0 +1,114 @@ +#Author: Joaquin Recio, Jose Luis Huertas and Juan Paulo Soto + +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=- Felicidades por iniciar CAS correctamente! El m\u00e9todo de autentifiaci\u00f3n por defecto es el nombre de usuario igual a la contrase\u00f1a : adelante, pru\u00e9belo. +screen.welcome.security=Por razones de seguridad, por favor cierre su sesi\u00f3n y su navegador web cuando haya terminado de acceder a los servicios que requieren autentificaci\u00f3n. +screen.welcome.instructions=Introduzca su NetID y Contrase\u00f1a. +screen.welcome.label.netid=NetID: +screen.welcome.label.netid.accesskey=n +screen.welcome.label.password=Contrase\u00f1a: +screen.welcome.label.password.accesskey=c +screen.welcome.label.warn=Avisarme antes de abrir sesi\u00f3n en otros sitios. +screen.welcome.label.warn.accesskey=a +screen.welcome.button.login=INICIAR SESI\u00d3N +screen.welcome.button.clear=LIMPIAR + +# Blocked Errors Page +screen.blocked.header=Acceso Denegado +screen.blocked.message=Haz ingresado mal la contrase\u00f1a muchas veces. + +#Confirmation Screen Messages +screen.confirmation.message=Haga clic aqu\u00ed para ir a la aplicaci\u00f3n. + +#Generic Success Screen Messages +screen.success.header=Inicio de sesi\u00f3n correcto. +screen.success.success=Ha iniciado sesi\u00f3n satisfactoriamente en el Servicio de Autentificaci\u00f3n Central. +screen.success.security=Por razones de seguridad, por favor cierre su sesi\u00f3n y su navegador web cuando haya terminado de acceder a los servicios que requieren autentificaci\u00f3n. + +#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. +screen.logout.security=Por razones de seguridad, cierre su navegador web. +screen.logout.redirect=El servicio desde el cual ha accedido ha proporcionado un enlace al que puede acceder haciendo clic aqu\u00ed. +screen.service.sso.error.header=Reautentificaci\u00f3n es requerida para accesar este servicio. +screen.service.sso.error.message=Intenta accesar un servicio que requiere autenticaci\u00f3n sin re-autenticaci\u00f3n . Por favor intente autenticarse nuevamente. + +error.invalid.loginticket=No puede intentar reenviar un formulario que ya ha sido enviado. +required.username=El nombre de usuario es un campo obligatorio. +required.password=La contrase\u00f1a es un campo obligatorio. +error.authentication.credentials.bad=No se puede determinar que las credenciales proporcionadas sean aut\u00e9nticas. +error.authentication.credentials.unsupported=Las credenciales proporcionadas no est\u00e1n soportadas por CAS. + +INVALID_REQUEST_PROXY=los par\u00e1metros 'pgt' y 'targetService' son obligatorios. +INVALID_TICKET_SPEC=El ticket fall\u00f3 la especificaci\u00f3n de validaci\u00f3n. Los errores posibles incluyen validar un ticket de proxy (Proxy Ticket) utilizando un validador de tickets de servicio (Service Ticket), o no cumplir con la petici\u00f3n de renovaci\u00f3n (renew true). +INVALID_REQUEST=los par\u00e1metros 'service' y 'ticket' son obligatorios + +INVALID_TICKET=no se ha reconocido el ticket ''{0}'' +INVALID_SERVICE=el ticket ''{0}'' no coincide con el servicio proporcionado. El servicio original es ''{1}'' y el servicio suministrado es ''{2}''. + +# SERVICES MANAGEMENT +addServiceView=Agregar Nuevo Servicio +editServiceView=Editar Servicio +manageServiceView=Administrar Servicios +viewStatisticsView=Ver Estad\u00edsticas + +screen.service.error.header=La aplicaci\u00f3n no esta autorizada para usar CAS +screen.service.error.message=La aplicaci\u00f3n que se intenta autentificar no esta autorizada para usar CAS. + +registeredService.serviceId.exists=Un Servicio con la misma URL de servicio ya existe. + +application.title=Jasig 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.allowedToProxy=Permitido hacer proxy +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.allowedToProxy=Proxy permitido +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=borrar +management.services.service.warn=CAS actualmente esta ejecut\u00e1ndose en "modo abierto", debido a que no hay servicios configurados con esta herramienta. Luego de configurar un servicio en esta herramienta, CAS no sera considerado como "abierto", y cualquier aplicaci\u00f3n que desee utilizar CAS, debe ser registrada en esta herramienta. Esto incluye ESTA HERRAMIENTA. Si vas a utilizar esta herramienta, EL PRIMER SERVICIO A AGREGAR ES ESTA MISMA. La URL por defecto del la Herramienta de Administraci\u00f3n de Servicios es "{0}". 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 0000000..77680d6 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_fa.properties @@ -0,0 +1,116 @@ +#Welcome Screen Messages + +# +# 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. +# + +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 + +# 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. + + +# 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 +viewStatisticsView=\u0645\u0634\u0627\u0647\u062F\u0647 \u0622\u0645\u0627\u0631 + +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. + +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=Jasig 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.add.instructions=\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.allowedToProxy=\u0627\u062C\u0627\u0632\u0647 \u067E\u0631\u0627\u06A9\u0633\u06CC \u062F\u0627\u0631\u062F +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.allowedToProxy=\u0627\u062C\u0627\u0632\u0647 \u067E\u0631\u0627\u06A9\u0633\u06CC \u062F\u0627\u0631\u062F +management.services.manage.label.ssoParticipant=\u062E\u0631\u0648\u062C \u06CC\u06A9\u0628\u0627\u0631\u0647 +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 + +management.services.service.warn=CAS \u062F\u0631 \u062D\u0627\u0644 \u062D\u0627\u0636\u0631 \u0628\u0647 \u0634\u06CC\u0648\u0647\u06CC \u0628\u0627\u0632 \u0627\u062C\u0631\u0627 \u0645\u06CC\u0634\u0648\u062F \u0632\u06CC\u0631\u0627 \u0647\u06CC\u0686 \u0633\u0631\u0648\u06CC\u0633\u06CC \u0645\u0637\u0627\u0628\u0642 \u0628\u0627 \u0627\u06CC\u0646 \u0627\u0628\u0632\u0627\u0631 \u0622\u0631\u0627\u06CC\u0634 \u0646\u06CC\u0627\u0641\u062A\u0647 \u0627\u0633\u062A. \u0632\u0645\u0627\u0646\u06CC\u06A9\u0647 \u0627\u06CC\u0646 \u0627\u0628\u0632\u0627\u0631 \u0631\u0627 \u0628\u0631\u0627\u06CC \u062F\u0627\u0634\u062A\u0646 \u0633\u0631\u0648\u06CC\u0633\u06CC \u062A\u0637\u0628\u06CC\u0642 \u062F\u0647\u06CC\u062F\u060C CAS \u062F\u06CC\u06AF\u0631 \u0628\u0627\u0632 \u0646\u062E\u0648\u0627\u0647\u062F \u0628\u0648\u062F\u060C \u0628\u0646\u0627\u0628\u0631\u0627\u06CC\u0646 \u0647\u0631 \u0628\u0631\u0646\u0627\u0645\u0647\u0627\u06CC \u06A9\u0647 \u0628\u062E\u0648\u0627\u0647\u062F \u0627\u0632 CAS \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u06A9\u0646\u062F \u0628\u0627\u06CC\u062F \u062F\u0631 \u0627\u06CC\u0646 \u0627\u0628\u0632\u0627\u0631 \u062B\u0628\u062A \u0634\u0648\u062F \u06A9\u0647 \u0634\u0627\u0645\u0644 \u0627\u06CC\u0646 \u0627\u0628\u0632\u0627\u0631 \u0645\u06CC\u0634\u0648\u062F. \u0627\u06AF\u0631 \u0642\u0635\u062F \u062F\u0627\u0631\u06CC\u062F \u0627\u0632 \u0627\u06CC\u0646 \u0627\u0628\u0632\u0627\u0631 \u0627\u0633\u062A\u0641\u0627\u062F\u0647 \u06A9\u0646\u06CC\u062F\u060C \u0627\u0648\u0644\u06CC\u0646 \u0633\u0631\u0648\u06CC\u0633\u06CC \u06A9\u0647 \u0628\u0627\u06CC\u062F \u0627\u0636\u0627\u0641\u0647 \u06A9\u0646\u06CC\u062F \u0647\u0645\u06CC\u0646 \u0633\u0631\u0648\u06CC\u0633 \u0627\u0633\u062A. URL \u0627\u0628\u0632\u0627\u0631 \u0645\u062F\u06CC\u0631\u06CC\u062A \u0633\u0631\u0648\u06CC\u0633 \u067E\u06CC\u0634 \u0641\u0631\u0636 "{0}" \u0627\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 0000000..c7f1774 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_fr.properties @@ -0,0 +1,154 @@ +#Author: Pascal Aubry +#Version: $Revision$ $Date$ +#Since: 3.0.4 + +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=Flicitations, votre serveur est en ligne ! Vou pouvez maintenant essayer le service d'authentification par dfaut, qui authentifie lorsque le mot de passe est gal au nom d'utilisateur. +screen.welcome.security=Pour des raisons de scurit, veuillez vous dconnecter et fermer votre navigateur lorsque vous avez fini d'accder aux services authentifis. +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.warn=Prvenez-moi avant d'accder d'autres services. +screen.welcome.label.warn.accesskey=p +screen.welcome.button.login=SE CONNECTER +screen.welcome.button.clear=EFFACER + +# Blocked Errors Page +screen.blocked.header=Accs non autoris +screen.blocked.message=Vous avez saisi un mauvais mot de passe trop de fois de suite. Vous avez t bloqu. + +#Confirmation Screen Messages +screen.confirmation.message=Cliquez ici pour accder au service. + +#Generic Success Screen Messages +screen.success.header=Connexion russie +screen.success.success=Vous vous tes authentifi(e) auprs du Service Central d'Authentification. +screen.success.security=Pour des raisons de scurit, veuillez vous dconnecter et fermer votre navigateur lorsque vous avez fini d'accder aux services authentifis. + +#Logout Screen Messages +screen.logout.header=Dconnexion russie +screen.logout.success=Vous vous tes dconnect(e) du Service Central d'Authentification. +screen.logout.security=Pour des raisons de scurit, veuillez fermer votre navigateur. +screen.logout.redirect=Le service duquel vous arrivez a fourni un lien que vous pouvez suivre en cliquant ici. + + +error.invalid.loginticket=Vous ne pouvez pas re-soumettre un formulaire d'autentification qui a dj t soumis. +required.username=Vous devez entrer votre identifiant. +required.password=Vous devez entrer votre mot de passe. +error.authentication.credentials.bad=Les informations transmises n'ont pas permis de vous authentifier. +error.authentication.credentials.unsupported=Les informations d'authentifications transmises ne sont pas supportes par CAS. + +INVALID_REQUEST_PROXY=Les paramtres 'pgt' et 'targetService' sont tous deux ncessaires +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 requte de type renew. +INVALID_REQUEST=Les paramtres 'service' et 'ticket' sont tous deux ncessaires +INVALID_TICKET=le ticket ''{0}'' est inconnu +INVALID_SERVICE=le ticket ''{0}'' ne correspond pas au service demand + +# SERVICES MANAGEMENT +addServiceView=Ajouter un nouveau service +editServiceView=Editer le service +manageServiceView=Grer les services + +screen.service.error.header=Application non autorise utiliser CAS +screen.service.error.message=L'application pour laquelle vous avez tent de vous authentifier n'est pas autorise utiliser CAS. +screen.service.sso.error.header=Une nouvelle authentification est requise pour accder ce service. +screen.service.sso.error.message=Vous avez tent d'accder un service qui requiert une nouvelle authentification sans vous authentifier nouveau. Veuillez vous authentifier de nouveau. + +registeredService.serviceId.exists=Un service avec cette URL existe dj. + +application.title=Service Central d'Authentification Jasig +application.errors.global=Veuillez corriger les erreurs ci-dessous : + +management.services.title=Gestion des services +management.services.link.logout=Dconnexion + +management.services.status.notdeleted=Le service ne peut pas tre supprim. +management.services.status.deleted={0} a t supprim. + +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 thme +management.services.add.property.status=Etat +management.services.add.property.status.enabled=Activ +management.services.add.property.status.allowedToProxy=Autoris au mode proxy +management.services.add.property.status.ssoParticipant=Participant au SSO +management.services.add.property.status.anonymousAccess=Accs anonyme +management.services.add.property.attributes=Attributs + +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.allowedToProxy=Peut faire du proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=diter +management.services.manage.action.delete=supprimer + +viewStatisticsView=Voir les statistiques +management.services.add.property.ignoreAttributes=Ignorer la gestion des attributs par cet outil +management.services.add.property.evaluationOrder=Ordre + +management.services.service.warn=Ce serveur d'authentification CAS fonctionne actuellement en mode "ouvert" (open mode) car aucun service n'a encore t configur.
A partir du moment o un service, au moins, aura t configur, ce serveur d'authentification ne fonctionnera plus en mode "ouvert"; et de ce fait, tous services/applications souhaitant utiliser ce serveur pour l'authentification devront tre pralablement configurs. Cette ncessit d'enregistrement concerne galement cet outil d'enregistrement. Vous devez donc commencer par enregistrer cet outil en tant que nouveau service en le rattachant l'URL + +# LPPE Account Error +screen.accounterror.password.message=La date de changement du mot de passe n'est pas spcifie, est incorrecte ou invalide. Merci de contacter votre administrateur systme pour rcuprer votre accs. + +# LPPE Account Disabled +screen.accountdisabled.heading=Ce compte a t dsactiv. +screen.accountdisabled.message=Merci de contacter votre administrateur systme pour rcuprer votre accs. + +# LPPE Password Expired +screen.expiredpass.heading=Votre mot de passe a expir. +screen.expiredpass.message=Merci de changer votre mot de passe. + +# LPPE Password Must be changed +screen.mustchangepass.heading=Vous devez changer votre mot de passe. +screen.mustchangepass.message=Merci de changer votre mot de passe. + +# LPPE Login out of authorized hours +screen.badhours.heading=Vous ne pouvez pas vous authentifier durant cette plage horaire. +screen.badhours.message=Merci de ressayer plus tard. + +# LPPE Login out of authorized workstations +screen.badworkstation.heading=Vous ne pouvez pas vous authentifier depuis cet ordinateur. +screen.badworkstation.message=Merci de contacter votre administrateur systme pour rcuprer votre accs. + +# LPPE Password Warning +screen.warnpass.heading.today=Votre mot de passe expire aujourd'hui! +screen.warnpass.heading.tomorrow=Votre mot de passe expire demain! +screen.warnpass.heading.other=Votre mot de passe expire dans {0} jours. +screen.warnpass.message.line1=Merci de changer votre mot de passe maintenant. +screen.warnpass.message.line2=Vous allez tre redirig automatiquement vers votre application dans 10 secondes. + +# OAuth +screen.oauth.confirm.header=Autorisation +screen.oauth.confirm.message=Voulez-vous donner accs votre profil complet "{0}" ? +screen.oauth.confirm.allow=Autoriser 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 0000000..9cfee05 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_hr.properties @@ -0,0 +1,115 @@ +# +# 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. +# + +# @author Nebojsa Topolscak +# @author Jasmina Plavac +# University Computing Center - Zagreb, Croatia +# @version $Revision$ $Date$ +# @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}''. + + +# SERVICES MANAGEMENT +addServiceView=Dodaj novi servis +editServiceView=Uredi servis +manageServiceView=Upravljanje servisima + +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. + +registeredService.serviceId.exists=Servis s ovim URL-om ve\u0107 postoji. + +application.title=Jasig 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.allowedToProxy=Dozvoljen proxy +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.allowedToProxy=Dozvoljen proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=Uredi +management.services.manage.action.delete=Obri\u0161i + + 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 0000000..add8b47 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_it.properties @@ -0,0 +1,149 @@ +#Author: Roberto Cosenza http://robcos.com +#Version: $Revision$ $Date$ +#Since: 3.0.5 + +#Welcome Screen Messages + +# +# 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. +# + +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 + +# SERVICES MANAGEMENT +addServiceView=Aggiungere nuovo servizio +editServiceView=Modificare servizio +manageServiceView=Gestire i servizi +viewStatisticsView=Vedere statistiche + +#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 + +registeredService.serviceId.exists=Un servizio con questa URL esiste gi. + +application.title=Jasig 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.allowedToProxy=Autorizzazione a fare da proxy +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.allowedToProxy=Autorizzazione a fare da proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=modifica +management.services.manage.action.delete=elimina + +management.services.service.warn=CAS sta funzionando in "open mode" perch nessun servizio configurato attraverso questa interfaccia di gestione. Appena sar stato configurato il primo servizio, CAS non sar pi considerato "open" e tutte le applicazioni che desiderano servirsi di CAS dovranno essere registrate, inclusa QUESTA INTERFACCIA. Se si vuole usare questo strumento, il PRIMO SERVIZIO DA AGGIUNGERE QUESTO STESSO SERVIZIO. Lo strumento di gestione servizi di default "{0}". + +# 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 0000000..d05c39b --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ja.properties @@ -0,0 +1,86 @@ +#Author: Shoji Kajita +#Version: $Revision$ $Date$ +#Since: 3.1 + +#Welcome Screen Messages + +# +# 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. +# + +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=\u6a29\u9650\u306e\u306a\u3044\u30b5\u30fc\u30d3\u30b9 +screen.service.error.message=\u8a8d\u8a3c\u3057\u3088\u3046\u3068\u3057\u305f\u30b5\u30fc\u30d3\u30b9\u306b\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 + + +// ### 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 + + +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 + +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-server-webapp/src/main/resources/messages_mk.properties b/cas-server-webapp/src/main/resources/messages_mk.properties new file mode 100644 index 0000000..b882e85 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_mk.properties @@ -0,0 +1,113 @@ +# +# 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. +# + +\ufeff# @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 +# @version $Revision$ $Date$ +# @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}''. + + +# 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 + +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. + +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=Jasig \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.allowedToProxy=\u0414\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e \u043f\u043e\u0441\u0440\u0435\u0434\u0443\u0432\u0430\u045a\u0435 +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.allowedToProxy=\u0414\u043e\u0437\u0432\u043e\u043b\u0435\u043d\u043e \u043f\u043e\u0441\u0440\u0435\u0434\u0443\u0432\u0430\u045a\u0435 +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-server-webapp/src/main/resources/messages_nl.properties b/cas-server-webapp/src/main/resources/messages_nl.properties new file mode 100644 index 0000000..890a381 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_nl.properties @@ -0,0 +1,108 @@ +#Author: Jan "Velpi" Van der Velpen +#Version $Revision$ $Date$ +#Since 3.0.3 + +#Welcome Screen Messages + +# +# 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. +# + +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. + + + +# SERVICES MANAGEMENT +addServiceView=Service Toevoegen +editServiceView=Service Bewerken +manageServiceView=Services Beheren + +screen.service.error.header=Geen toegang. +screen.service.error.message=De applicatie waarvoor je toegang vroeg heeft geen toestemming om deze CAS te gebruiken. + +registeredService.serviceId.exists=Er bestaat al een Service met die Service Url. + +application.title=Jasig 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.allowedToProxy=Proxy toegelaten +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.allowedToProxy=Proxy toegelaten +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=bewerken +management.services.manage.action.delete=verwijderen 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 0000000..b4e9d18 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_pl.properties @@ -0,0 +1,142 @@ +# @author Maja Gorecka-Wolniewicz +# @version $Revision$ $Date$ +# @since 3.1.1 + +#Welcome Screen Messages + +# +# 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. +# + +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.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 + +#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. + +error.invalid.loginticket=Nie mo\u017cesz ponownie wys\u0142a\u0107 formularza wcze\u015bniej wys\u0142anego. +required.username=Nazwa u\u017cytkownika jest polem wymaganym. +required.password=Has\u0142o jest polem wymaganym. +error.authentication.credentials.bad=Dostarczone dane uwierzytelniania nie mog\u0105 zosta\u0107 uznane za poprawne. +error.authentication.credentials.unsupported=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}''. + + +# SERVICES MANAGEMENT +addServiceView=Dodaj now\u0105 us\u0142ug\u0119 +editServiceView=Edytuj us\u0142ug\u0119 +manageServiceView=Zarz\u0105dzaj us\u0142ugami + +screen.service.error.header=Brak uprawnie\u0144 do korzystania z CAS +screen.service.error.message=Aplikacja, w kt\u00f3ej chcia\u0142e\u015b zosta\u0107 uwierzytelniony nie ma uprawnie\u0144 do korzystania z CAS. + +registeredService.serviceId.exists=Us\u0142uga o takim adresie ju\u017c istnieje. + +application.title=Us\u0142uga Centralnego Uwierzytelniania (Jasig 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.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.allowedToProxy=Dozwolone proxy +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.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.allowedToProxy=Dozwolone proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=edytuj +management.services.manage.action.delete=usu\u0144 + +management.services.service.warn=CAS obecnie pracuje w trybie "otwartym" poniewa\u017c \u017cadna us\u0142uga nie zosta\u0142a skonfigurowana. Gdy zostanie dodana pierwsza us\u0142uga CAS przestanie pracowa\u0107 w trybie otwarty i ka\u017cda aplikacja, kt\u00f3ra mia\u0142aby wsp\u00f3\u0142pracowa\u0107 z CASem musi zosta\u0107 w nim zarejestrowana. Dotyczy to r\u00f3wnie\u017c TEGO NARZ\u0118DZIA. Je\u017celi planujesz u\u017cywa\u0107 tego narz\u0119dzia, musisz je zarejstrowa\u0107 jako us\u0142ug\u0119 w CASie. Domy\u015blny adres tego narz\u0119dzia to: "{0}" + +# LPPE Account Error +screen.accounterror.password.message=Termin wa\u017cno\u015bci has\u0142a up\u0142yn\u0105\u0142, nie jest okre\u015blony, albo ustawiona data jest nieprawid\u0142owa. Prosz\u0119 skontaktowa\u0107 si\u0119 z pomoc\u0105 techniczn\u0105 w celu uzyskania dost\u0119pu. + +# LPPE Account Disabled +screen.accountdisabled.heading=Konto zosta\u0142o zablokowane. +screen.accountdisabled.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. + +# LPPE Password Warning +screen.warnpass.heading.today=Dzisiaj up\u0142ywa termin wa\u017cno\u015bci Twojego has\u0142a! +screen.warnpass.heading.tomorrow=Jutro up\u0142ywa termin wa\u017cno\u015bci Twojego has\u0142a! +screen.warnpass.heading.other=Termin wa\u017cno\u015bci Twojego has\u0142a wygasa za {0} dni. +screen.warnpass.message.line1=Prosz\u0119 natychmiast zmieni\u0107 has\u0142o. +screen.warnpass.message.line2=Za 10 sekund nast\u0105pi przekierowanie to wybranej aplikacji. 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 0000000..8d3983f --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_pt_BR.properties @@ -0,0 +1,109 @@ +#Welcome Screen Messages + +# +# 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. +# + +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}''. + + +# SERVICES MANAGEMENT +addServiceView=Adicionar Novo Servi\u00e7o +editServiceView=Editar Servi\u00e7o +manageServiceView=Gerenciar Servi\u00e7os +viewStatisticsView=Ver Estat\u00edsticas + +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. + +registeredService.serviceId.exists=Um servi\u00e7o com essa URL de servi\u00e7o j\u00e1 existe. + +application.title=Jasig 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.allowedToProxy=Permitido Fazer Proxy +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.allowedToProxy=Pode Fazer Proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=excluir 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 0000000..c7f9a8a --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_pt_PT.properties @@ -0,0 +1,116 @@ +#Welcome Screen Messages + +# +# 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. +# + +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}''. + + +# SERVICES MANAGEMENT +addServiceView=Adicionar Servi\u221a\u00dfo +editServiceView=Editar Servi\u221a\u00dfo +manageServiceView=Gerir Servi\u221a\u00dfos +viewStatisticsView=Ver Estat\u221a\u2260sticas + +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 + +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 Jasig +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.allowedToProxy=Pode fazer Proxy +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.allowedToProxy=Pode fazer Proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=editar +management.services.manage.action.delete=apagar + +management.services.service.warn=O Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central est\u221a\u00b0 a ser executado em "modo aberto" porque n\u221a\u00a3o existem servi\u221a\u00dfos configurados. Assim que configurar esta ferramenta para ter um servi\u221a\u00dfo, o Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central deixar\u221a\u00b0 de funcionar em "modo aberto" e assim, qualquer aplica\u221a\u00df\u221a\u00a3o que deseje usar o Servi\u221a\u00dfo de Autentica\u221a\u00df\u221a\u00a3o Central deve ser registada. Isto inclui ESTA FERRAMENTA. Se a vai usar, O PRIMEIRO SERVI\u221a\u00e1O A SER ADICIONADO \u221a\u00e2 O SERVI\u221a\u00e1O EM SI. O URL por defeito para a ferramenta de gest\u221a\u00a3o de servi\u221a\u00dfos \u221a\u00a9 "{0}". 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 0000000..1446102 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ru.properties @@ -0,0 +1,102 @@ +#Welcome Screen Messages + +# +# 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. +# + +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=n +screen.welcome.label.password=\u041f\u0430\u0440\u043e\u043b\u044c: +screen.welcome.label.password.accesskey=p +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=\u043f +screen.welcome.button.login=\u0412\u041e\u0419\u0422\u0418 +screen.welcome.button.clear=\u041e\u0427\u0418\u0421\u0422\u0418\u0422\u044c + +#Confirmation Screen Messages +screen.confirmation.message=\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u043d\u0430 \u0441\u0441\u044b\u043b\u043a\u0443 \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443. + +#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. + +#Service Error Messages +screen.service.error.header=\u0421\u0435\u0440\u0432\u0438\u0441 \u0431\u0435\u0437 \u043f\u0440\u0430\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430. +screen.service.error.message=\u0421\u0435\u0440\u0432\u0438\u0441, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u0430 \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u0438, \u043d\u0435 \u0438\u043c\u0435\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u0438\u0441\u0442\u0435\u043c\u0435 CAS. + + +error.invalid.loginticket=\u0412\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0442\u043e\u0441\u043b\u0430\u0442\u044c \u0432\u0435\u0431-\u0444\u043e\u0440\u043c\u0443, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0443\u0436\u0435 \u043e\u0442\u043e\u0441\u043b\u0430\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. +error.authentication.credentials.bad=\u041f\u043e\u0434\u043b\u0438\u043d\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u0432\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0430. +error.authentication.credentials.unsupported=\u041f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0432\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u043e\u0439 CAS. + +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. +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.service.warn=CAS \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0432 "\u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c" \u0440\u0435\u0436\u0438\u043c\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043d\u0438 \u043e\u0434\u0438\u043d \u0441\u0435\u0440\u0432\u0438\u0441 \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0439 \u0443\u0442\u0438\u043b\u0438\u0442\u044b. \u041f\u043e\u0441\u043b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438, CAS \u0431\u043e\u043b\u0435\u0435 \u043d\u0435 \u0431\u0443\u0434\u0435\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0437\u0434\u0435\u0441\u044c \u0432\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0431\u0443\u0434\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c CAS \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u0415\u0441\u043b\u0438 \u0432\u044b \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0435 \u044d\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c, \u0441\u043d\u0430\u0447\u0430\u043b\u0430 \u043d\u0443\u0436\u043d\u043e \u0414\u041e\u0411\u0410\u0412\u0418\u0422\u042c \u0421\u0410\u041c \u0421\u0415\u0420\u0412\u0418\u0421. URL \u0441\u0435\u0440\u0432\u0438\u0441\u0430 \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f "{0}". +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.allowedToProxy=\u041f\u043e\u0437\u0432\u043e\u043b\u0438\u0442\u044c \u043f\u0440\u043e\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 +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.allowedToProxy=\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u043f\u0440\u043e\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 +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 +screen.blocked.message=\u0412\u044b \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0440\u0430\u0437 \u0432\u0432\u043e\u0434\u0438\u043b\u0438 \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c. \u0412\u0430\u0448 \u0430\u043a\u043a\u0430\u0443\u043d\u0442 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d. +screen.service.sso.error.header=\u0414\u043b\u044f \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0441\u0435\u0440\u0432\u0438\u0441\u0443 \u043d\u0443\u0436\u043d\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f +screen.service.sso.error.message=\u0412\u044b \u043f\u044b\u0442\u0430\u0435\u0442\u0435\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\u044b\u0439 \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044e \u0431\u0435\u0437 \u043f\u043e\u0432\u0442\u043e\u0440\u043e\u0439 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u043d\u043e\u0432\u0430. +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=Jasig Central Authentication Service +editServiceView=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0432\u0438\u0441 +viewStatisticsView=\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443 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 0000000..85a0df7 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_sl.properties @@ -0,0 +1,63 @@ +#Welcome Screen Messages + +# +# 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. +# + +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 0000000..85f07c0 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_sv.properties @@ -0,0 +1,109 @@ +#Author: Fredrik Nilsson http://www.infoflexconnect.se +#Updated 2006-08-29: Pl Axelsson & Veronika Berglund IT Support Department at Uppsala University http://www.uu.se +#Updated 2007-06-21: Pl Axelsson IT Support Department at Uppsala University http://www.uu.se + +#Welcome Screen Messages + +# +# 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. +# + +screen.welcome.welcome=Vlkommen till den centrala autentiseringstjnsten CAS. Nr du installerat men nnu inte konfigurerat CAS kan du autentisera genom att ange samma text som bde anvndaridentitet och lsenord fr att prova CAS. +screen.welcome.security=Av skerhetsskl br du logga ut och stnga webblsaren nr du r frdig med webbtjnsterna som krver inloggning. +screen.welcome.instructions=Ange din anvndaridentitet och ditt lsenord. +screen.welcome.label.netid=Anvndarid: +screen.welcome.label.netid.accesskey=a +screen.welcome.label.password=Lsenord: +screen.welcome.label.password.accesskey=l +screen.welcome.label.warn=Varna mig innan jag loggar p en annan webbtjnst. +screen.welcome.label.warn.accesskey=v +screen.welcome.button.login=LOGGA IN +screen.welcome.button.clear=RENSA + +#Confirmation Screen Messages +screen.confirmation.message=Klicka hr fr att komma till webbtjnsten. + +#Generic Success Screen Messages +screen.success.header=Inloggningen lyckades +screen.success.success=Du har loggat in i den centrala autentiseringstjnsten CAS. +screen.success.security=Av skerhetsskl br du logga ut och stnga webblsaren nr du r frdig med webbtjnsterna som krver inloggning. + +#Logout Screen Messages +screen.logout.header=Du har loggat ut! +screen.logout.success=Du har loggat ut frn den centrala autentiseringstjnsten CAS. +screen.logout.security=Av skerhetsskl br du stnga din webblsare. +screen.logout.redirect=Du kan logga in igen genom att klicka hr. + +screen.service.sso.error.header=Du mste logga in igen fr att anvnda denna webbtjnst +screen.service.sso.error.message=Du frskte anvnda en webbtjnst som krver att du loggar in igen fr att anvnda den. Logga in igen! + +error.invalid.loginticket=Du kan inte teranvnda ett webbformulr som redan har skickats in. +required.username=Anvndaridentitet r en obligatoriskt uppgift. +required.password=Lsenord 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=Bde 'pgt' och 'targetService' r obligatoriska parametrar. +INVALID_TICKET_SPEC=Ticket-valideringen misslyckades. Mjliga fel skulle kunna vara att frska validera en Proxy Ticket via en validator fr Service Ticket, eller att en ny inloggning inte genomfrdes trots begran. +INVALID_REQUEST=Bde 'service' och 'ticket' r obligatoriska parametrar. +INVALID_TICKET=ticket ''{0}'' knns inte igen. +INVALID_SERVICE=ticket ''{0}'' verenstmmer inte med angiven webbtjnst. + +# SERVICES MANAGEMENT +addServiceView=Lgg till ny webbtjnst +editServiceView=ndra webbtjnst +manageServiceView=Hantera webbtjnst + +screen.service.error.header=Ej auktoriserad webbtjnst +screen.service.error.message=Webbtjnsten du frskter ansluta till r ej auktoriserad att anvnda den centrala autentiseringstjnsten CAS. + +registeredService.serviceId.exists=En webbtjnst med denna webbadress finns redan. + +application.title=Jasig Central Authentication Service +application.errors.global=Rtta felen nedan: + +management.services.title=Webbtjnstehantering +management.services.link.logout=LOGGA UT + +management.services.status.notdeleted=Webbtjnsten kan inte tas bort. +management.services.status.deleted=Webbtjnsten {0} har blivit borttagen. + +management.services.add.instructions=Kom ihg att klicka p knappen SPARA lgst ner p sidan fr att spara ndringarna. +management.services.add.property.name=Webbtjnst +management.services.add.property.serviceUrl=Webbadress +management.services.add.property.serviceUrl.instructions=Du kan anvnda 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.allowedToProxy=Fr agera proxy +management.services.add.property.status.ssoParticipant=Anvnder SSO +management.services.add.property.status.anonymousAccess=Anonym tillgng +management.services.add.property.attributes=Attribut + +management.services.add.button.save=SPARA +management.services.add.button.cancel=AVBRYT + +management.services.manage.label.name=Webbtjnst +management.services.manage.label.serviceUrl= Webbadress +management.services.manage.label.enabled=Aktiv +management.services.manage.label.allowedToProxy=Kan agera proxy +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=NDRA +management.services.manage.action.delete=TA BORT 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 0000000..d978e65 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_tr.properties @@ -0,0 +1,111 @@ +# Author : Mert Caliskan +# http://www.jroller.com/mert + +#Welcome Screen Messages + +# +# 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. +# + +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}''. + + +# SERVICES MANAGEMENT +addServiceView=Yeni Servis Ekle +editServiceView=Servisi G\u00fcncelle +manageServiceView=Servisleri Y\u00f6net + +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. + +registeredService.serviceId.exists=Servis URL bilgisine sahip servis bulunmaktad\u0131r. + +application.title=Jasig 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.allowedToProxy=Vekile izin var (Proxy) +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.allowedToProxy=Vekilleyebilir (Proxy) +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=g\u00fcncelle +management.services.manage.action.delete=sil 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 0000000..ed0f872 --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_ur.properties @@ -0,0 +1,67 @@ +#Author: Faizan Ahmed (Rutgers University) +#Version $Revision$ $Date$ +#Since 3.0.5 + +#Welcome Screen Messages + +# +# 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. +# + +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 Jasig 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 0000000..9a763cd --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_zh_CN.properties @@ -0,0 +1,114 @@ +#Welcome Screen Messages + +# +# 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. +# + +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 + +# 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 +error.authentication.credentials.bad=\u60a8\u63d0\u4f9b\u7684\u51ed\u8bc1\u6709\u8bef\u3002 +error.authentication.credentials.unsupported=CAS\u4e0d\u652f\u6301\u60a8\u63d0\u4f9b\u7684\u51ed\u8bc1\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 + +# SERVICES MANAGEMENT +addServiceView=\u6dfb\u52a0\u670d\u52a1 +editServiceView=\u7f16\u8f91\u670d\u52a1 +manageServiceView=\u7ba1\u7406\u670d\u52a1 +viewStatisticsView=\u7edf\u8ba1\u4fe1\u606f + +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 + +registeredService.serviceId.exists=\u5177\u6709\u540c\u4e00\u670d\u52a1URL\u7684\u670d\u52a1\u5df2\u7ecf\u5b58\u5728\u3002 + +application.title=Jasig\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.allowedToProxy=\u5141\u8bb8\u4f5c\u4e3a\u4ee3\u7406 +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.allowedToProxy=\u5177\u6709\u4ee3\u7406\u80fd\u529b +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=\u7f16\u8f91 +management.services.manage.action.delete=\u5220\u9664 + +management.services.service.warn=CAS\u5f53\u524d\u8fd0\u884c\u5728\u201c\u5f00\u653e\u6a21\u5f0f\u201d\u4e0b\uff0c\u56e0\u4e3a\u6ca1\u6709\u914d\u7f6e\u4efb\u4f55service\u670d\u52a1\u3002\u5982\u679c\u4f60\u5728\u8fd9\u91cc\u914d\u7f6e\u4e86\u670d\u52a1\uff0cCAS\u5c31\u4e0d\u518d\u662f\u5b8c\u5168\u5f00\u653e\u7684\u4e86\uff0c\u6240\u4ee5\u9700\u8981\u4f7f\u7528CAS\u7684\u5e94\u7528\u90fd\u8981\u5728\u8fd9\u91cc\u6ce8\u518c\u3002\u8fd9\u4e5f\u5305\u62ec\u8fd9\u4e2a\u7ba1\u7406\u540e\u53f0\u3002\u5982\u679c\u4f60\u60f3\u4f7f\u7528\u8fd9\u4e2a\u7ba1\u7406\u540e\u53f0\uff0c\u9996\u5148\u8981\u628a\u7ba1\u7406\u540e\u53f0\u6dfb\u52a0\u4e3a\u7b2c\u4e00\u4e2a\u670d\u52a1\u3002\u9ed8\u8ba4\u7684\u670d\u52a1\u7ba1\u7406URL\u662f"{0}"\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 0000000..bc61acf --- /dev/null +++ b/cas-server-webapp/src/main/resources/messages_zh_TW.properties @@ -0,0 +1,114 @@ +#Welcome Screen Messages + +# +# 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. +# + +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 + +# SERVICES MANAGEMENT +addServiceView=\u6dfb\u52a0\u670d\u52d9 +editServiceView=\u7de8\u8f2f\u670d\u52d9 +manageServiceView=\u7ba1\u7406\u670d\u52d9 +viewStatisticsView=\u7d71\u8a08\u4fe1\u606f + +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 + +registeredService.serviceId.exists=\u5177\u6709\u540c\u4e00\u670d\u52d9URL\u7684\u670d\u52d9\u5df2\u7d93\u5b58\u5728\u3002 + +application.title=Jasig\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.allowedToProxy=\u5141\u8a31\u4f5c\u70ba\u4ee3\u7406 +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.allowedToProxy=\u5177\u6709\u4ee3\u7406\u80fd\u529b +management.services.manage.label.ssoParticipant=SSO + +management.services.manage.action.edit=\u7de8\u8f2f +management.services.manage.action.delete=\u522a\u9664 + +management.services.service.warn=CAS\u7576\u524d\u904b\u884c\u5728\u201c\u958b\u653e\u6a21\u5f0f\u201d\u4e0b\uff0c\u56e0\u70ba\u6c92\u6709\u914d\u7f6e\u4efb\u4f55service\u670d\u52d9\u3002\u5982\u679c\u4f60\u5728\u9019\u88e1\u914d\u7f6e\u4e86\u670d\u52d9\uff0cCAS\u5c31\u4e0d\u518d\u662f\u5b8c\u5168\u958b\u653e\u7684\u4e86\uff0c\u6240\u4ee5\u9700\u8981\u4f7f\u7528CAS\u7684\u61c9\u7528\u90fd\u8981\u5728\u9019\u88e1\u8a3b\u518a\u3002\u9019\u4e5f\u5305\u62ec\u9019\u500b\u7ba1\u7406\u5f8c\u53f0\u3002\u5982\u679c\u4f60\u60f3\u4f7f\u7528\u9019\u500b\u7ba1\u7406\u5f8c\u53f0\uff0c\u9996\u5148\u8981\u628a\u7ba1\u7406\u5f8c\u53f0\u6dfb\u52a0\u70ba\u7b2c\u4e00\u500b\u670d\u52d9\u3002\u9ed8\u8a8d\u7684\u670d\u52d9\u7ba1\u7406URL\u662f"{0}"\u3002 diff --git a/cas-server-webapp/src/main/resources/protocol_views.properties b/cas-server-webapp/src/main/resources/protocol_views.properties new file mode 100644 index 0000000..e74d4d8 --- /dev/null +++ b/cas-server-webapp/src/main/resources/protocol_views.properties @@ -0,0 +1,68 @@ +# +# 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. +# + +### 1.0 validation responses (/validate) +cas1ServiceFailureView.(class)=org.jasig.cas.web.view.Cas10ResponseView +cas1ServiceFailureView.successResponse=false + +cas1ServiceSuccessView.(class)=org.jasig.cas.web.view.Cas10ResponseView +cas1ServiceSuccessView.successResponse=true + +### CAS 2.0 Response Protocol Views +casServiceSuccessView.(class)=org.springframework.web.servlet.view.JstlView +casServiceSuccessView.url=/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp + +casServiceFailureView.(class)=org.springframework.web.servlet.view.JstlView +casServiceFailureView.url=/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp + +casProxyFailureView.(class)=org.springframework.web.servlet.view.JstlView +casProxyFailureView.url=/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp + +casProxySuccessView.(class)=org.springframework.web.servlet.view.JstlView +casProxySuccessView.url=/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp + +### SAML Views +casSamlServiceSuccessView.(class)=org.jasig.cas.web.view.Saml10SuccessResponseView +casSamlServiceSuccessView.issuer=localhost + +casSamlServiceFailureView.(class)=org.jasig.cas.web.view.Saml10FailureResponseView + +#OpenId Views +casOpenIdServiceFailureView.(class)=org.springframework.web.servlet.view.JstlView +casOpenIdServiceFailureView.url=/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp + +casOpenIdServiceSuccessView.(class)=org.springframework.web.servlet.view.JstlView +casOpenIdServiceSuccessView.url=/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp + +casOpenIdAssociationFailureView.(class)=org.springframework.web.servlet.view.JstlView +casOpenIdAssociationFailureView.url=/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationFailureView.jsp + +casOpenIdAssociationSuccessView.(class)=org.springframework.web.servlet.view.JstlView +casOpenIdAssociationSuccessView.url=/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationSuccessView.jsp + +openIdProviderView.(class)=org.springframework.web.servlet.view.JstlView +openIdProviderView.url=/WEB-INF/view/jsp/protocol/openid/user.jsp + +### Post View +postResponseView.(class)=org.springframework.web.servlet.view.JstlView +postResponseView.url=/WEB-INF/view/jsp/protocol/casPostResponseView.jsp + +### OAuth View +oauthConfirmView.(class)=org.springframework.web.servlet.view.JstlView +oauthConfirmView.url=/WEB-INF/view/jsp/protocol/oauth/confirm.jsp 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 0000000..06defc4 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/cas-servlet.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + ${cas.viewResolver.basename} + protocol_views + + + + + + + + + + + + + + + + + + + + + + + + logoutController + serviceValidateController + legacyValidateController + proxyController + proxyValidateController + samlValidateController + addRegisteredServiceSimpleFormController + editRegisteredServiceSimpleFormController + serviceLogoutViewController + viewStatisticsController + manageRegisteredServicesMultiActionController + openIdProviderController + passThroughController + passThroughController + healthCheckController + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..b78ebc3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/cas.properties @@ -0,0 +1,105 @@ +# +# 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. +# + +## +# Services Management Web UI Security +server.name=http://localhost:8080 +server.prefix=${server.name}/cas +cas.securityContext.serviceProperties.service=${server.prefix}/services/j_acegi_cas_security_check +# Names of roles allowed to access the CAS service manager +cas.securityContext.serviceProperties.adminRoles=ROLE_ADMIN +cas.securityContext.casProcessingFilterEntryPoint.loginUrl=${server.prefix}/login +cas.securityContext.ticketValidator.casServerUrlPrefix=${server.prefix} +# 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.basename=default_views + +## +# 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 + +## +# CAS Logout Behavior +# WEB-INF/cas-servlet.xml +# +# Specify whether CAS should redirect to the specifyed service parameter on /logout requests +# cas.logout.followServiceRedirects=false + +## +# 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 + +## +# 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. +# 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/src/main/webapp/WEB-INF/deployerConfigContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml new file mode 100644 index 0000000..1f10542 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/deployerConfigContext.xml @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/login-webflow.xml b/cas-server-webapp/src/main/webapp/WEB-INF/login-webflow.xml new file mode 100644 index 0000000..8b3d2fa --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/login-webflow.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..774a0be --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/restlet-servlet.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..e3f2cbf --- /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 0000000..b0c9ea3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/applicationContext.xml @@ -0,0 +1,111 @@ + + + + + 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! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..559e44e --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/argumentExtractorsConfiguration.xml @@ -0,0 +1,46 @@ + + + + + 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 CAS and SAML are 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 0000000..42a2e93 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/auditTrailContext.xml @@ -0,0 +1,162 @@ + + + + + + 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 0000000..88b9912 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/filters.xml @@ -0,0 +1,29 @@ + + + + + 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 0000000..5498f1c --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/log4jConfiguration.xml @@ -0,0 +1,46 @@ + + + + + + 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. + + + + + + + + ${log4j.config.location:classpath:log4j.xml} + ${log4j.refresh.interval:60000} + + + + + 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 0000000..402717f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/propertyFileConfigurer.xml @@ -0,0 +1,35 @@ + + + + + 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/securityContext.xml b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml new file mode 100644 index 0000000..bc56277 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/securityContext.xml @@ -0,0 +1,88 @@ + + + + + + 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..0fae629 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketExpirationPolicies.xml @@ -0,0 +1,59 @@ + + + + + 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 0000000..5c2eca0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml @@ -0,0 +1,36 @@ + + + + + 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 0000000..9307d5f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/ticketRegistry.xml @@ -0,0 +1,46 @@ + + + + + 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 0000000..f30d941 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/uniqueIdGenerators.xml @@ -0,0 +1,89 @@ + + + + + Controls the generation of the unique identifiers for tickets. You most likely do not need to modify these. Though you may need to modify + 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 0000000..2f0a404 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/spring-configuration/warnCookieGenerator.xml @@ -0,0 +1,36 @@ + + + + + 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 0000000..181a49d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/clearpass-configuration.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..e149754 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/lppe-configuration.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000..f4f13b9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/unused-spring-configuration/mbeans.xml @@ -0,0 +1,53 @@ + + + + + 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 0000000..788e912 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/authorizationFailure.jsp @@ -0,0 +1,60 @@ +<%-- + + 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. + +--%> + + +<%@ page import="org.jasig.cas.web.support.WebUtils"%> +<%@ page import="org.springframework.security.web.WebAttributes"%> + + +
+

+ <% + // Look for details of authorization failure in well-known request attributes. + final String[] keys = new String[] {WebUtils.CAS_ACCESS_DENIED_REASON, WebAttributes.AUTHENTICATION_EXCEPTION}; + Object detail = null; + for (String key : keys) { + detail = request.getAttribute(key); + if (detail == null) { + detail = request.getSession().getAttribute(key); + request.getSession().removeAttribute(key); + } + if (detail != null) { + break; + } + } + if (detail instanceof String) { + request.setAttribute("messageKey", detail); + } else if (detail instanceof Exception) { + final Exception cause = (Exception) detail; + final String message = String.format("%s::%s", cause.getClass().getSimpleName(), cause.getMessage()); + request.setAttribute("message", message); + } + %> + + +

+
+ +

+
+
+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/brokenContext.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/brokenContext.jsp new file mode 100644 index 0000000..59ced7f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/brokenContext.jsp @@ -0,0 +1,63 @@ +<%-- + + 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. + +--%> + +
+

CAS is Unavailable

+ +

+ There was a fatal error initializing the CAS application context. This is almost always because of an error in the Spring bean configuration files. + Are the files valid XML? Do the beans they refer to all exist?

+ Before placing CAS in production, you should change this page to present a UI appropriate for the case where the CAS + web application is fundamentally broken. Perhaps "Sorry, CAS is currently unavailable." with some links to your user support information. +

+ + +

+ The Throwable representing the fatal error has been logged by the SafeDispatcherServlet + via Commons Logging, via ServletContext logging, and to System.err. +

+
+ + +

+ The Throwable representing the fatal error has been logged by the SafeContextLoaderListener + via Commons Logging, via ServletContext logging, and to System.err. +

+
+ + + + +

+ The Throwable encountered at context listener initialization was:

+ +

+
+ + +

+ The Throwable encountered at dispatcher servlet initialization was:

+ +

+
+
+ + 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 0000000..20b524c --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountDisabledView.jsp @@ -0,0 +1,26 @@ +<%-- + + 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. + +--%> + +
+

+

+
+ 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 0000000..5824863 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casAccountLockedView.jsp @@ -0,0 +1,26 @@ +<%-- + + 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. + +--%> + +
+

+

+
+ 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 0000000..3df6228 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadHoursView.jsp @@ -0,0 +1,25 @@ +<%-- + + 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. + +--%> + +
+

+

+ 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 0000000..6a1f1e9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casBadWorkstationView.jsp @@ -0,0 +1,25 @@ +<%-- + + 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. + +--%> + +
+

+

+ 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 0000000..ea0ffbc --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casConfirmView.jsp @@ -0,0 +1,23 @@ +<%-- + + 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. + +--%> + +

+ 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 0000000..6de96c1 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casExpiredPassView.jsp @@ -0,0 +1,25 @@ +<%-- + + 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. + +--%> + +
+

+

+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp new file mode 100644 index 0000000..602fabb --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp @@ -0,0 +1,28 @@ +<%-- + + 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. + +--%> + +
+

+

+

+
+ + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.default.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.default.jsp new file mode 100644 index 0000000..2de1909 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.default.jsp @@ -0,0 +1,142 @@ +<%-- + + 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. + +--%> + + + +
+

Non-secure Connection

+

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.

+
+
+ +
+ + + +

+
+ + + ${sessionScope.openIdLocalId} + + + + + + + +
+
+ + <%-- + 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.geocities.com/technofundo/tech/web/ie_autocomplete.html + --%> + + +
+
+ " type="checkbox" /> + + +
+
+ + + + + " tabindex="4" type="submit" /> + " tabindex="5" type="reset" /> +
+
+
+ + 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 0000000..aaa2589 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.jsp @@ -0,0 +1,136 @@ +<%@ page session="true" %> +<%@ 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" %> + + + + + + + + + + +创力 | 登录 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.metronic.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.metronic.jsp new file mode 100644 index 0000000..48dc133 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.metronic.jsp @@ -0,0 +1,142 @@ +<%@ page session="true" %> +<%@ 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" %> + + + + + + + + + + + +Metronic | Login Options - Login Form 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.modify.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.modify.jsp new file mode 100644 index 0000000..8ab588c --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLoginView.modify.jsp @@ -0,0 +1,71 @@ +<%-- + + 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. + +--%> +<%@ page contentType="text/html;charset=UTF-8"%> + + +
+ + + +

+
+ + + ${sessionScope.openIdLocalId} + + + + + + + +
+
+ + <%-- + 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.geocities.com/technofundo/tech/web/ie_autocomplete.html + --%> + + +
+
+ +
+
+ + + + " tabindex="4" type="submit" /> + " tabindex="5" 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 0000000..74d5b31 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casLogoutView.jsp @@ -0,0 +1,29 @@ +<%-- + + 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. + +--%> + + +
+

+ +

+

+
+ 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 0000000..6b83910 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casMustChangePassView.jsp @@ -0,0 +1,25 @@ +<%-- + + 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. + +--%> + +
+

+

+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casWarnPassView.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casWarnPassView.jsp new file mode 100644 index 0000000..160abaf --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/casWarnPassView.jsp @@ -0,0 +1,55 @@ +<%-- + + 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. + +--%> + + + + + + +
+

+ +

+ + +

+
+ +

+
+

+ +

+ +

+

+ +

+
+ 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 0000000..b892d20 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/bottom.jsp @@ -0,0 +1,36 @@ +<%-- + + 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. + +--%> +<%@ 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 0000000..7dffce5 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/includes/top.jsp @@ -0,0 +1,22 @@ +<%@ page session="true" %> +<%@ 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" /> + + +
+
+ +
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 0000000..ef00e10 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorSsoView.jsp @@ -0,0 +1,31 @@ +<%-- + + 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. + +--%> + + + + + + +
+

+

+
+ 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 0000000..7fa390a --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/default/ui/serviceErrorView.jsp @@ -0,0 +1,26 @@ +<%-- + + 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. + +--%> + +
+

+

+
+ 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 0000000..e620bd0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/errors.jsp @@ -0,0 +1,29 @@ +<%-- + + 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. + +--%> + +
+

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-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 0000000..c4ada19 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxyFailureView.jsp @@ -0,0 +1,27 @@ +<%-- + + 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. + +--%> +<%@ page session="false" contentType="text/plain" %> +<%@ 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 0000000..291fbfb --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casProxySuccessView.jsp @@ -0,0 +1,27 @@ +<%-- + + 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. + +--%> +<%@ page session="false" contentType="text/plain" %> +<%@ 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 0000000..c5eec7f --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationFailure.jsp @@ -0,0 +1,27 @@ +<%-- + + 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. + +--%> +<%@ page session="false" contentType="text/plain" %> +<%@ 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 0000000..3417bc0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp @@ -0,0 +1,38 @@ +<%-- + + 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. + +--%> +<%@ 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)} + + + + + 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 0000000..35478f3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/casPostResponseView.jsp @@ -0,0 +1,37 @@ +<%-- + + 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. + +--%> +<%@ page language="java" session="false"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> + + +
" method="post"> +
+ + + +
+ +
+ + 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 0000000..1b2e7f8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassFailure.jsp @@ -0,0 +1,25 @@ +<%-- + + 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. + +--%> +<%@ page session="false" contentType="text/plain" %> +<%@ 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 0000000..5d0d2f6 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/clearPass/clearPassSuccess.jsp @@ -0,0 +1,28 @@ +<%-- + + 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. + +--%> +<%@ 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(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 0000000..16ff8d6 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/oauth/confirm.jsp @@ -0,0 +1,32 @@ +<%-- + + 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. + +--%> + +
+

+ +

+ +

+

+ +

+
+ 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 0000000..07f5227 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationFailureView.jsp @@ -0,0 +1,21 @@ +<%-- + + 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. + +--%> +<%= "openid.mode:cancel\n" %> 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 0000000..7ef2952 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdAssociationSuccessView.jsp @@ -0,0 +1,30 @@ +<%-- + + 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. + +--%> +<%@ 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"); + } +%> 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 0000000..009b6ff --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceFailureView.jsp @@ -0,0 +1,21 @@ +<%-- + + 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. + +--%> +<%= "openid.mode:id_res\nis_valid:false\n" %> 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 0000000..0982bcc --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/casOpenIdServiceSuccessView.jsp @@ -0,0 +1,21 @@ +<%-- + + 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. + +--%> +<%= "openid.mode:id_res\nis_valid:true\n" %> 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 0000000..a268556 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/openid/user.jsp @@ -0,0 +1,25 @@ +<%-- + + 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. + +--%> + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/add.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/add.jsp new file mode 100644 index 0000000..5805cb3 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/add.jsp @@ -0,0 +1,119 @@ +<%-- + + 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. + +--%> +<%@include file="includes/top.jsp"%> + + + +
${successMessage}
+
+ + +
+ +
+
+
+
+

+ + + + +
+
+ + + + + +
+
+
+ + + + + + +
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ + or +
+
+<%@include file="includes/bottom.jsp" %> diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/bottom.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/bottom.jsp new file mode 100644 index 0000000..4dd3eac --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/bottom.jsp @@ -0,0 +1,44 @@ +<%-- + + 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. + +--%> +
+ + + + + + + + diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/top.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/top.jsp new file mode 100644 index 0000000..6e59331 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/includes/top.jsp @@ -0,0 +1,69 @@ +<%-- + + 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. + +--%> + +<%@ 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="image/x-icon" /> + " type="text/css" /> + + + + + + + + + + + + + +
+

diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/logout.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/logout.jsp new file mode 100644 index 0000000..25bb095 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/logout.jsp @@ -0,0 +1,26 @@ +<%-- + + 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. + +--%> + +
+

+

+
+ diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/manage.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/manage.jsp new file mode 100644 index 0000000..db7e2f1 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/manage.jsp @@ -0,0 +1,71 @@ +<%-- + + 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. + +--%> +<%@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'}${service.anonymousAccess ? 'Anonyous Access Enabled' : 'Anonyous Access Disabled'}${service.usernameAttribute}${service.evaluationOrder}
+
+
+<%@include file="includes/bottom.jsp" %> diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/viewStatistics.jsp b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/viewStatistics.jsp new file mode 100644 index 0000000..b1f7726 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/services/viewStatistics.jsp @@ -0,0 +1,102 @@ +<%-- + + 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. + +--%> +<%@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" %> diff --git a/cas-server-webapp/src/main/webapp/WEB-INF/view/views.xml b/cas-server-webapp/src/main/webapp/WEB-INF/view/views.xml new file mode 100644 index 0000000..7386c1d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/view/views.xml @@ -0,0 +1,30 @@ + + + + + + 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 0000000..9e0d3aa --- /dev/null +++ b/cas-server-webapp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,240 @@ + + + + Central Authentication System (CAS) ${project.version} + + + contextConfigLocation + + /WEB-INF/spring-configuration/*.xml + /WEB-INF/deployerConfigContext.xml + + + + + CAS Client Info Logging Filter + com.github.inspektr.common.web.ClientInfoThreadLocalFilter + + + CAS Client Info Logging Filter + /* + + + + springSecurityFilterChain + org.springframework.web.filter.DelegatingFilterProxy + + + springSecurityFilterChain + /status + + + springSecurityFilterChain + /services/* + + + + characterEncodingFilter + org.springframework.web.filter.DelegatingFilterProxy + + + characterEncodingFilter + /* + + + + + + 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 + /services/updateRegisteredServiceEvaluationOrder.html + + + + cas + /status + + + + 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 + + diff --git a/cas-server-webapp/src/main/webapp/assets/css/animate.css b/cas-server-webapp/src/main/webapp/assets/css/animate.css new file mode 100644 index 0000000..8dfcfe9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/animate.css @@ -0,0 +1,3263 @@ +@charset "UTF-8"; +/* +Animate.css - http://daneden.me/animate +Licensed under the MIT license + +Copyright (c) 2013 Daniel Eden + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +body { /* Addresses a small issue in webkit: http://bit.ly/NEdoDq */ + -webkit-backface-visibility: hidden; +} +.animated { + -webkit-animation-duration: 1s; + -moz-animation-duration: 1s; + -o-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + -moz-animation-fill-mode: both; + -o-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.hinge { + -webkit-animation-duration: 2s; + -moz-animation-duration: 2s; + -o-animation-duration: 2s; + animation-duration: 2s; +} + +@-webkit-keyframes flash { + 0%, 50%, 100% {opacity: 1;} + 25%, 75% {opacity: 0;} +} + +@-moz-keyframes flash { + 0%, 50%, 100% {opacity: 1;} + 25%, 75% {opacity: 0;} +} + +@-o-keyframes flash { + 0%, 50%, 100% {opacity: 1;} + 25%, 75% {opacity: 0;} +} + +@keyframes flash { + 0%, 50%, 100% {opacity: 1;} + 25%, 75% {opacity: 0;} +} + +.flash { + -webkit-animation-name: flash; + -moz-animation-name: flash; + -o-animation-name: flash; + animation-name: flash; +} +@-webkit-keyframes shake { + 0%, 100% {-webkit-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-webkit-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-webkit-transform: translateX(10px);} +} + +@-moz-keyframes shake { + 0%, 100% {-moz-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-moz-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-moz-transform: translateX(10px);} +} + +@-o-keyframes shake { + 0%, 100% {-o-transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {-o-transform: translateX(-10px);} + 20%, 40%, 60%, 80% {-o-transform: translateX(10px);} +} + +@keyframes shake { + 0%, 100% {transform: translateX(0);} + 10%, 30%, 50%, 70%, 90% {transform: translateX(-10px);} + 20%, 40%, 60%, 80% {transform: translateX(10px);} +} + +.shake { + -webkit-animation-name: shake; + -moz-animation-name: shake; + -o-animation-name: shake; + animation-name: shake; +} +@-webkit-keyframes bounce { + 0%, 20%, 50%, 80%, 100% {-webkit-transform: translateY(0);} + 40% {-webkit-transform: translateY(-30px);} + 60% {-webkit-transform: translateY(-15px);} +} + +@-moz-keyframes bounce { + 0%, 20%, 50%, 80%, 100% {-moz-transform: translateY(0);} + 40% {-moz-transform: translateY(-30px);} + 60% {-moz-transform: translateY(-15px);} +} + +@-o-keyframes bounce { + 0%, 20%, 50%, 80%, 100% {-o-transform: translateY(0);} + 40% {-o-transform: translateY(-30px);} + 60% {-o-transform: translateY(-15px);} +} +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% {transform: translateY(0);} + 40% {transform: translateY(-30px);} + 60% {transform: translateY(-15px);} +} + +.bounce { + -webkit-animation-name: bounce; + -moz-animation-name: bounce; + -o-animation-name: bounce; + animation-name: bounce; +} +@-webkit-keyframes tada { + 0% {-webkit-transform: scale(1);} + 10%, 20% {-webkit-transform: scale(0.9) rotate(-3deg);} + 30%, 50%, 70%, 90% {-webkit-transform: scale(1.1) rotate(3deg);} + 40%, 60%, 80% {-webkit-transform: scale(1.1) rotate(-3deg);} + 100% {-webkit-transform: scale(1) rotate(0);} +} + +@-moz-keyframes tada { + 0% {-moz-transform: scale(1);} + 10%, 20% {-moz-transform: scale(0.9) rotate(-3deg);} + 30%, 50%, 70%, 90% {-moz-transform: scale(1.1) rotate(3deg);} + 40%, 60%, 80% {-moz-transform: scale(1.1) rotate(-3deg);} + 100% {-moz-transform: scale(1) rotate(0);} +} + +@-o-keyframes tada { + 0% {-o-transform: scale(1);} + 10%, 20% {-o-transform: scale(0.9) rotate(-3deg);} + 30%, 50%, 70%, 90% {-o-transform: scale(1.1) rotate(3deg);} + 40%, 60%, 80% {-o-transform: scale(1.1) rotate(-3deg);} + 100% {-o-transform: scale(1) rotate(0);} +} + +@keyframes tada { + 0% {transform: scale(1);} + 10%, 20% {transform: scale(0.9) rotate(-3deg);} + 30%, 50%, 70%, 90% {transform: scale(1.1) rotate(3deg);} + 40%, 60%, 80% {transform: scale(1.1) rotate(-3deg);} + 100% {transform: scale(1) rotate(0);} +} + +.tada { + -webkit-animation-name: tada; + -moz-animation-name: tada; + -o-animation-name: tada; + animation-name: tada; +} +@-webkit-keyframes swing { + 20%, 40%, 60%, 80%, 100% { -webkit-transform-origin: top center; } + 20% { -webkit-transform: rotate(15deg); } + 40% { -webkit-transform: rotate(-10deg); } + 60% { -webkit-transform: rotate(5deg); } + 80% { -webkit-transform: rotate(-5deg); } + 100% { -webkit-transform: rotate(0deg); } +} + +@-moz-keyframes swing { + 20% { -moz-transform: rotate(15deg); } + 40% { -moz-transform: rotate(-10deg); } + 60% { -moz-transform: rotate(5deg); } + 80% { -moz-transform: rotate(-5deg); } + 100% { -moz-transform: rotate(0deg); } +} + +@-o-keyframes swing { + 20% { -o-transform: rotate(15deg); } + 40% { -o-transform: rotate(-10deg); } + 60% { -o-transform: rotate(5deg); } + 80% { -o-transform: rotate(-5deg); } + 100% { -o-transform: rotate(0deg); } +} + +@keyframes swing { + 20% { transform: rotate(15deg); } + 40% { transform: rotate(-10deg); } + 60% { transform: rotate(5deg); } + 80% { transform: rotate(-5deg); } + 100% { transform: rotate(0deg); } +} + +.swing { + -webkit-transform-origin: top center; + -moz-transform-origin: top center; + -o-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + -moz-animation-name: swing; + -o-animation-name: swing; + animation-name: swing; +} +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + 0% { -webkit-transform: translateX(0%); } + 15% { -webkit-transform: translateX(-25%) rotate(-5deg); } + 30% { -webkit-transform: translateX(20%) rotate(3deg); } + 45% { -webkit-transform: translateX(-15%) rotate(-3deg); } + 60% { -webkit-transform: translateX(10%) rotate(2deg); } + 75% { -webkit-transform: translateX(-5%) rotate(-1deg); } + 100% { -webkit-transform: translateX(0%); } +} + +@-moz-keyframes wobble { + 0% { -moz-transform: translateX(0%); } + 15% { -moz-transform: translateX(-25%) rotate(-5deg); } + 30% { -moz-transform: translateX(20%) rotate(3deg); } + 45% { -moz-transform: translateX(-15%) rotate(-3deg); } + 60% { -moz-transform: translateX(10%) rotate(2deg); } + 75% { -moz-transform: translateX(-5%) rotate(-1deg); } + 100% { -moz-transform: translateX(0%); } +} + +@-o-keyframes wobble { + 0% { -o-transform: translateX(0%); } + 15% { -o-transform: translateX(-25%) rotate(-5deg); } + 30% { -o-transform: translateX(20%) rotate(3deg); } + 45% { -o-transform: translateX(-15%) rotate(-3deg); } + 60% { -o-transform: translateX(10%) rotate(2deg); } + 75% { -o-transform: translateX(-5%) rotate(-1deg); } + 100% { -o-transform: translateX(0%); } +} + +@keyframes wobble { + 0% { transform: translateX(0%); } + 15% { transform: translateX(-25%) rotate(-5deg); } + 30% { transform: translateX(20%) rotate(3deg); } + 45% { transform: translateX(-15%) rotate(-3deg); } + 60% { transform: translateX(10%) rotate(2deg); } + 75% { transform: translateX(-5%) rotate(-1deg); } + 100% { transform: translateX(0%); } +} + +.wobble { + -webkit-animation-name: wobble; + -moz-animation-name: wobble; + -o-animation-name: wobble; + animation-name: wobble; +} +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + 0% { -webkit-transform: scale(1); } + 50% { -webkit-transform: scale(1.1); } + 100% { -webkit-transform: scale(1); } +} +@-moz-keyframes pulse { + 0% { -moz-transform: scale(1); } + 50% { -moz-transform: scale(1.1); } + 100% { -moz-transform: scale(1); } +} +@-o-keyframes pulse { + 0% { -o-transform: scale(1); } + 50% { -o-transform: scale(1.1); } + 100% { -o-transform: scale(1); } +} +@keyframes pulse { + 0% { transform: scale(1); } + 50% { transform: scale(1.1); } + 100% { transform: scale(1); } +} + +.pulse { + -webkit-animation-name: pulse; + -moz-animation-name: pulse; + -o-animation-name: pulse; + animation-name: pulse; +} +@-webkit-keyframes flip { + 0% { + -webkit-transform: perspective(400px) rotateY(0); + -webkit-animation-timing-function: ease-out; + } + 40% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg); + -webkit-animation-timing-function: ease-out; + } + 50% { + -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + -webkit-animation-timing-function: ease-in; + } + 80% { + -webkit-transform: perspective(400px) rotateY(360deg) scale(.95); + -webkit-animation-timing-function: ease-in; + } + 100% { + -webkit-transform: perspective(400px) scale(1); + -webkit-animation-timing-function: ease-in; + } +} +@-moz-keyframes flip { + 0% { + -moz-transform: perspective(400px) rotateY(0); + -moz-animation-timing-function: ease-out; + } + 40% { + -moz-transform: perspective(400px) translateZ(150px) rotateY(170deg); + -moz-animation-timing-function: ease-out; + } + 50% { + -moz-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + -moz-animation-timing-function: ease-in; + } + 80% { + -moz-transform: perspective(400px) rotateY(360deg) scale(.95); + -moz-animation-timing-function: ease-in; + } + 100% { + -moz-transform: perspective(400px) scale(1); + -moz-animation-timing-function: ease-in; + } +} +@-o-keyframes flip { + 0% { + -o-transform: perspective(400px) rotateY(0); + -o-animation-timing-function: ease-out; + } + 40% { + -o-transform: perspective(400px) translateZ(150px) rotateY(170deg); + -o-animation-timing-function: ease-out; + } + 50% { + -o-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + -o-animation-timing-function: ease-in; + } + 80% { + -o-transform: perspective(400px) rotateY(360deg) scale(.95); + -o-animation-timing-function: ease-in; + } + 100% { + -o-transform: perspective(400px) scale(1); + -o-animation-timing-function: ease-in; + } +} +@keyframes flip { + 0% { + transform: perspective(400px) rotateY(0); + animation-timing-function: ease-out; + } + 40% { + transform: perspective(400px) translateZ(150px) rotateY(170deg); + animation-timing-function: ease-out; + } + 50% { + transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1); + animation-timing-function: ease-in; + } + 80% { + transform: perspective(400px) rotateY(360deg) scale(.95); + animation-timing-function: ease-in; + } + 100% { + transform: perspective(400px) scale(1); + animation-timing-function: ease-in; + } +} + +.flip { + -webkit-backface-visibility: visible !important; + -webkit-animation-name: flip; + -moz-backface-visibility: visible !important; + -moz-animation-name: flip; + -o-backface-visibility: visible !important; + -o-animation-name: flip; + backface-visibility: visible !important; + animation-name: flip; +} +@-webkit-keyframes flipInX { + 0% { + -webkit-transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateX(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} +@-moz-keyframes flipInX { + 0% { + -moz-transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -moz-transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -moz-transform: perspective(400px) rotateX(10deg); + } + + 100% { + -moz-transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} +@-o-keyframes flipInX { + 0% { + -o-transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + -o-transform: perspective(400px) rotateX(-10deg); + } + + 70% { + -o-transform: perspective(400px) rotateX(10deg); + } + + 100% { + -o-transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} +@keyframes flipInX { + 0% { + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } + + 40% { + transform: perspective(400px) rotateX(-10deg); + } + + 70% { + transform: perspective(400px) rotateX(10deg); + } + + 100% { + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + -webkit-animation-name: flipInX; + -moz-backface-visibility: visible !important; + -moz-animation-name: flipInX; + -o-backface-visibility: visible !important; + -o-animation-name: flipInX; + backface-visibility: visible !important; + animation-name: flipInX; +} +@-webkit-keyframes flipOutX { + 0% { + -webkit-transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + 100% { + -webkit-transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +@-moz-keyframes flipOutX { + 0% { + -moz-transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + 100% { + -moz-transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +@-o-keyframes flipOutX { + 0% { + -o-transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + 100% { + -o-transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + 0% { + transform: perspective(400px) rotateX(0deg); + opacity: 1; + } + 100% { + transform: perspective(400px) rotateX(90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + -moz-animation-name: flipOutX; + -moz-backface-visibility: visible !important; + -o-animation-name: flipOutX; + -o-backface-visibility: visible !important; + animation-name: flipOutX; + backface-visibility: visible !important; +} +@-webkit-keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -webkit-transform: perspective(400px) rotateY(10deg); + } + + 100% { + -webkit-transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} +@-moz-keyframes flipInY { + 0% { + -moz-transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -moz-transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -moz-transform: perspective(400px) rotateY(10deg); + } + + 100% { + -moz-transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} +@-o-keyframes flipInY { + 0% { + -o-transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + -o-transform: perspective(400px) rotateY(-10deg); + } + + 70% { + -o-transform: perspective(400px) rotateY(10deg); + } + + 100% { + -o-transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} +@keyframes flipInY { + 0% { + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } + + 40% { + transform: perspective(400px) rotateY(-10deg); + } + + 70% { + transform: perspective(400px) rotateY(10deg); + } + + 100% { + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + -webkit-animation-name: flipInY; + -moz-backface-visibility: visible !important; + -moz-animation-name: flipInY; + -o-backface-visibility: visible !important; + -o-animation-name: flipInY; + backface-visibility: visible !important; + animation-name: flipInY; +} +@-webkit-keyframes flipOutY { + 0% { + -webkit-transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + 100% { + -webkit-transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} +@-moz-keyframes flipOutY { + 0% { + -moz-transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + 100% { + -moz-transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} +@-o-keyframes flipOutY { + 0% { + -o-transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + 100% { + -o-transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} +@keyframes flipOutY { + 0% { + transform: perspective(400px) rotateY(0deg); + opacity: 1; + } + 100% { + transform: perspective(400px) rotateY(90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + -moz-backface-visibility: visible !important; + -moz-animation-name: flipOutY; + -o-backface-visibility: visible !important; + -o-animation-name: flipOutY; + backface-visibility: visible !important; + animation-name: flipOutY; +} +@-webkit-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@-moz-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@-o-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.fadeIn { + -webkit-animation-name: fadeIn; + -moz-animation-name: fadeIn; + -o-animation-name: fadeIn; + animation-name: fadeIn; +} +@-webkit-keyframes fadeInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + } +} + +@-moz-keyframes fadeInUp { + 0% { + opacity: 0; + -moz-transform: translateY(20px); + } + + 100% { + opacity: 1; + -moz-transform: translateY(0); + } +} + +@-o-keyframes fadeInUp { + 0% { + opacity: 0; + -o-transform: translateY(20px); + } + + 100% { + opacity: 1; + -o-transform: translateY(0); + } +} + +@keyframes fadeInUp { + 0% { + opacity: 0; + transform: translateY(20px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + -moz-animation-name: fadeInUp; + -o-animation-name: fadeInUp; + animation-name: fadeInUp; +} +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + } +} + +@-moz-keyframes fadeInDown { + 0% { + opacity: 0; + -moz-transform: translateY(-20px); + } + + 100% { + opacity: 1; + -moz-transform: translateY(0); + } +} + +@-o-keyframes fadeInDown { + 0% { + opacity: 0; + -o-transform: translateY(-20px); + } + + 100% { + opacity: 1; + -o-transform: translateY(0); + } +} + +@keyframes fadeInDown { + 0% { + opacity: 0; + transform: translateY(-20px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + -moz-animation-name: fadeInDown; + -o-animation-name: fadeInDown; + animation-name: fadeInDown; +} +@-webkit-keyframes fadeInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + } +} + +@-moz-keyframes fadeInLeft { + 0% { + opacity: 0; + -moz-transform: translateX(-20px); + } + + 100% { + opacity: 1; + -moz-transform: translateX(0); + } +} + +@-o-keyframes fadeInLeft { + 0% { + opacity: 0; + -o-transform: translateX(-20px); + } + + 100% { + opacity: 1; + -o-transform: translateX(0); + } +} + +@keyframes fadeInLeft { + 0% { + opacity: 0; + transform: translateX(-20px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + -moz-animation-name: fadeInLeft; + -o-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} +@-webkit-keyframes fadeInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + } +} + +@-moz-keyframes fadeInRight { + 0% { + opacity: 0; + -moz-transform: translateX(20px); + } + + 100% { + opacity: 1; + -moz-transform: translateX(0); + } +} + +@-o-keyframes fadeInRight { + 0% { + opacity: 0; + -o-transform: translateX(20px); + } + + 100% { + opacity: 1; + -o-transform: translateX(0); + } +} + +@keyframes fadeInRight { + 0% { + opacity: 0; + transform: translateX(20px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + -moz-animation-name: fadeInRight; + -o-animation-name: fadeInRight; + animation-name: fadeInRight; +} +@-webkit-keyframes fadeInUpBig { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + } +} + +@-moz-keyframes fadeInUpBig { + 0% { + opacity: 0; + -moz-transform: translateY(2000px); + } + + 100% { + opacity: 1; + -moz-transform: translateY(0); + } +} + +@-o-keyframes fadeInUpBig { + 0% { + opacity: 0; + -o-transform: translateY(2000px); + } + + 100% { + opacity: 1; + -o-transform: translateY(0); + } +} + +@keyframes fadeInUpBig { + 0% { + opacity: 0; + transform: translateY(2000px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + -moz-animation-name: fadeInUpBig; + -o-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} +@-webkit-keyframes fadeInDownBig { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateY(0); + } +} + +@-moz-keyframes fadeInDownBig { + 0% { + opacity: 0; + -moz-transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -moz-transform: translateY(0); + } +} + +@-o-keyframes fadeInDownBig { + 0% { + opacity: 0; + -o-transform: translateY(-2000px); + } + + 100% { + opacity: 1; + -o-transform: translateY(0); + } +} + +@keyframes fadeInDownBig { + 0% { + opacity: 0; + transform: translateY(-2000px); + } + + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + -moz-animation-name: fadeInDownBig; + -o-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} +@-webkit-keyframes fadeInLeftBig { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + } +} +@-moz-keyframes fadeInLeftBig { + 0% { + opacity: 0; + -moz-transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -moz-transform: translateX(0); + } +} +@-o-keyframes fadeInLeftBig { + 0% { + opacity: 0; + -o-transform: translateX(-2000px); + } + + 100% { + opacity: 1; + -o-transform: translateX(0); + } +} +@keyframes fadeInLeftBig { + 0% { + opacity: 0; + transform: translateX(-2000px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + -moz-animation-name: fadeInLeftBig; + -o-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} +@-webkit-keyframes fadeInRightBig { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + } +} + +@-moz-keyframes fadeInRightBig { + 0% { + opacity: 0; + -moz-transform: translateX(2000px); + } + + 100% { + opacity: 1; + -moz-transform: translateX(0); + } +} + +@-o-keyframes fadeInRightBig { + 0% { + opacity: 0; + -o-transform: translateX(2000px); + } + + 100% { + opacity: 1; + -o-transform: translateX(0); + } +} + +@keyframes fadeInRightBig { + 0% { + opacity: 0; + transform: translateX(2000px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + -moz-animation-name: fadeInRightBig; + -o-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} +@-webkit-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@-moz-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@-o-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +.fadeOut { + -webkit-animation-name: fadeOut; + -moz-animation-name: fadeOut; + -o-animation-name: fadeOut; + animation-name: fadeOut; +} +@-webkit-keyframes fadeOutUp { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-20px); + } +} +@-moz-keyframes fadeOutUp { + 0% { + opacity: 1; + -moz-transform: translateY(0); + } + + 100% { + opacity: 0; + -moz-transform: translateY(-20px); + } +} +@-o-keyframes fadeOutUp { + 0% { + opacity: 1; + -o-transform: translateY(0); + } + + 100% { + opacity: 0; + -o-transform: translateY(-20px); + } +} +@keyframes fadeOutUp { + 0% { + opacity: 1; + transform: translateY(0); + } + + 100% { + opacity: 0; + transform: translateY(-20px); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + -moz-animation-name: fadeOutUp; + -o-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} +@-webkit-keyframes fadeOutDown { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(20px); + } +} + +@-moz-keyframes fadeOutDown { + 0% { + opacity: 1; + -moz-transform: translateY(0); + } + + 100% { + opacity: 0; + -moz-transform: translateY(20px); + } +} + +@-o-keyframes fadeOutDown { + 0% { + opacity: 1; + -o-transform: translateY(0); + } + + 100% { + opacity: 0; + -o-transform: translateY(20px); + } +} + +@keyframes fadeOutDown { + 0% { + opacity: 1; + transform: translateY(0); + } + + 100% { + opacity: 0; + transform: translateY(20px); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + -moz-animation-name: fadeOutDown; + -o-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} +@-webkit-keyframes fadeOutLeft { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-20px); + } +} + +@-moz-keyframes fadeOutLeft { + 0% { + opacity: 1; + -moz-transform: translateX(0); + } + + 100% { + opacity: 0; + -moz-transform: translateX(-20px); + } +} + +@-o-keyframes fadeOutLeft { + 0% { + opacity: 1; + -o-transform: translateX(0); + } + + 100% { + opacity: 0; + -o-transform: translateX(-20px); + } +} + +@keyframes fadeOutLeft { + 0% { + opacity: 1; + transform: translateX(0); + } + + 100% { + opacity: 0; + transform: translateX(-20px); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + -moz-animation-name: fadeOutLeft; + -o-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} +@-webkit-keyframes fadeOutRight { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(20px); + } +} + +@-moz-keyframes fadeOutRight { + 0% { + opacity: 1; + -moz-transform: translateX(0); + } + + 100% { + opacity: 0; + -moz-transform: translateX(20px); + } +} + +@-o-keyframes fadeOutRight { + 0% { + opacity: 1; + -o-transform: translateX(0); + } + + 100% { + opacity: 0; + -o-transform: translateX(20px); + } +} + +@keyframes fadeOutRight { + 0% { + opacity: 1; + transform: translateX(0); + } + + 100% { + opacity: 0; + transform: translateX(20px); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + -moz-animation-name: fadeOutRight; + -o-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} +@-webkit-keyframes fadeOutUpBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + } +} + +@-moz-keyframes fadeOutUpBig { + 0% { + opacity: 1; + -moz-transform: translateY(0); + } + + 100% { + opacity: 0; + -moz-transform: translateY(-2000px); + } +} + +@-o-keyframes fadeOutUpBig { + 0% { + opacity: 1; + -o-transform: translateY(0); + } + + 100% { + opacity: 0; + -o-transform: translateY(-2000px); + } +} + +@keyframes fadeOutUpBig { + 0% { + opacity: 1; + transform: translateY(0); + } + + 100% { + opacity: 0; + transform: translateY(-2000px); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + -moz-animation-name: fadeOutUpBig; + -o-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} +@-webkit-keyframes fadeOutDownBig { + 0% { + opacity: 1; + -webkit-transform: translateY(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + } +} + +@-moz-keyframes fadeOutDownBig { + 0% { + opacity: 1; + -moz-transform: translateY(0); + } + + 100% { + opacity: 0; + -moz-transform: translateY(2000px); + } +} + +@-o-keyframes fadeOutDownBig { + 0% { + opacity: 1; + -o-transform: translateY(0); + } + + 100% { + opacity: 0; + -o-transform: translateY(2000px); + } +} + +@keyframes fadeOutDownBig { + 0% { + opacity: 1; + transform: translateY(0); + } + + 100% { + opacity: 0; + transform: translateY(2000px); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + -moz-animation-name: fadeOutDownBig; + -o-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} +@-webkit-keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + } +} + +@-moz-keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -moz-transform: translateX(0); + } + + 100% { + opacity: 0; + -moz-transform: translateX(-2000px); + } +} + +@-o-keyframes fadeOutLeftBig { + 0% { + opacity: 1; + -o-transform: translateX(0); + } + + 100% { + opacity: 0; + -o-transform: translateX(-2000px); + } +} + +@keyframes fadeOutLeftBig { + 0% { + opacity: 1; + transform: translateX(0); + } + + 100% { + opacity: 0; + transform: translateX(-2000px); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + -moz-animation-name: fadeOutLeftBig; + -o-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} +@-webkit-keyframes fadeOutRightBig { + 0% { + opacity: 1; + -webkit-transform: translateX(0); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + } +} +@-moz-keyframes fadeOutRightBig { + 0% { + opacity: 1; + -moz-transform: translateX(0); + } + + 100% { + opacity: 0; + -moz-transform: translateX(2000px); + } +} +@-o-keyframes fadeOutRightBig { + 0% { + opacity: 1; + -o-transform: translateX(0); + } + + 100% { + opacity: 0; + -o-transform: translateX(2000px); + } +} +@keyframes fadeOutRightBig { + 0% { + opacity: 1; + transform: translateX(0); + } + + 100% { + opacity: 0; + transform: translateX(2000px); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + -moz-animation-name: fadeOutRightBig; + -o-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} +@-webkit-keyframes bounceIn { + 0% { + opacity: 0; + -webkit-transform: scale(.3); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.05); + } + + 70% { + -webkit-transform: scale(.9); + } + + 100% { + -webkit-transform: scale(1); + } +} + +@-moz-keyframes bounceIn { + 0% { + opacity: 0; + -moz-transform: scale(.3); + } + + 50% { + opacity: 1; + -moz-transform: scale(1.05); + } + + 70% { + -moz-transform: scale(.9); + } + + 100% { + -moz-transform: scale(1); + } +} + +@-o-keyframes bounceIn { + 0% { + opacity: 0; + -o-transform: scale(.3); + } + + 50% { + opacity: 1; + -o-transform: scale(1.05); + } + + 70% { + -o-transform: scale(.9); + } + + 100% { + -o-transform: scale(1); + } +} + +@keyframes bounceIn { + 0% { + opacity: 0; + transform: scale(.3); + } + + 50% { + opacity: 1; + transform: scale(1.05); + } + + 70% { + transform: scale(.9); + } + + 100% { + transform: scale(1); + } +} + +.bounceIn { + -webkit-animation-name: bounceIn; + -moz-animation-name: bounceIn; + -o-animation-name: bounceIn; + animation-name: bounceIn; +} +@-webkit-keyframes bounceInUp { + 0% { + opacity: 0; + -webkit-transform: translateY(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(-30px); + } + + 80% { + -webkit-transform: translateY(10px); + } + + 100% { + -webkit-transform: translateY(0); + } +} +@-moz-keyframes bounceInUp { + 0% { + opacity: 0; + -moz-transform: translateY(2000px); + } + + 60% { + opacity: 1; + -moz-transform: translateY(-30px); + } + + 80% { + -moz-transform: translateY(10px); + } + + 100% { + -moz-transform: translateY(0); + } +} + +@-o-keyframes bounceInUp { + 0% { + opacity: 0; + -o-transform: translateY(2000px); + } + + 60% { + opacity: 1; + -o-transform: translateY(-30px); + } + + 80% { + -o-transform: translateY(10px); + } + + 100% { + -o-transform: translateY(0); + } +} + +@keyframes bounceInUp { + 0% { + opacity: 0; + transform: translateY(2000px); + } + + 60% { + opacity: 1; + transform: translateY(-30px); + } + + 80% { + transform: translateY(10px); + } + + 100% { + transform: translateY(0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + -moz-animation-name: bounceInUp; + -o-animation-name: bounceInUp; + animation-name: bounceInUp; +} +@-webkit-keyframes bounceInDown { + 0% { + opacity: 0; + -webkit-transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateY(30px); + } + + 80% { + -webkit-transform: translateY(-10px); + } + + 100% { + -webkit-transform: translateY(0); + } +} + +@-moz-keyframes bounceInDown { + 0% { + opacity: 0; + -moz-transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -moz-transform: translateY(30px); + } + + 80% { + -moz-transform: translateY(-10px); + } + + 100% { + -moz-transform: translateY(0); + } +} + +@-o-keyframes bounceInDown { + 0% { + opacity: 0; + -o-transform: translateY(-2000px); + } + + 60% { + opacity: 1; + -o-transform: translateY(30px); + } + + 80% { + -o-transform: translateY(-10px); + } + + 100% { + -o-transform: translateY(0); + } +} + +@keyframes bounceInDown { + 0% { + opacity: 0; + transform: translateY(-2000px); + } + + 60% { + opacity: 1; + transform: translateY(30px); + } + + 80% { + transform: translateY(-10px); + } + + 100% { + transform: translateY(0); + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + -moz-animation-name: bounceInDown; + -o-animation-name: bounceInDown; + animation-name: bounceInDown; +} +@-webkit-keyframes bounceInLeft { + 0% { + opacity: 0; + -webkit-transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(30px); + } + + 80% { + -webkit-transform: translateX(-10px); + } + + 100% { + -webkit-transform: translateX(0); + } +} + +@-moz-keyframes bounceInLeft { + 0% { + opacity: 0; + -moz-transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -moz-transform: translateX(30px); + } + + 80% { + -moz-transform: translateX(-10px); + } + + 100% { + -moz-transform: translateX(0); + } +} + +@-o-keyframes bounceInLeft { + 0% { + opacity: 0; + -o-transform: translateX(-2000px); + } + + 60% { + opacity: 1; + -o-transform: translateX(30px); + } + + 80% { + -o-transform: translateX(-10px); + } + + 100% { + -o-transform: translateX(0); + } +} + +@keyframes bounceInLeft { + 0% { + opacity: 0; + transform: translateX(-2000px); + } + + 60% { + opacity: 1; + transform: translateX(30px); + } + + 80% { + transform: translateX(-10px); + } + + 100% { + transform: translateX(0); + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + -moz-animation-name: bounceInLeft; + -o-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} +@-webkit-keyframes bounceInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(2000px); + } + + 60% { + opacity: 1; + -webkit-transform: translateX(-30px); + } + + 80% { + -webkit-transform: translateX(10px); + } + + 100% { + -webkit-transform: translateX(0); + } +} + +@-moz-keyframes bounceInRight { + 0% { + opacity: 0; + -moz-transform: translateX(2000px); + } + + 60% { + opacity: 1; + -moz-transform: translateX(-30px); + } + + 80% { + -moz-transform: translateX(10px); + } + + 100% { + -moz-transform: translateX(0); + } +} + +@-o-keyframes bounceInRight { + 0% { + opacity: 0; + -o-transform: translateX(2000px); + } + + 60% { + opacity: 1; + -o-transform: translateX(-30px); + } + + 80% { + -o-transform: translateX(10px); + } + + 100% { + -o-transform: translateX(0); + } +} + +@keyframes bounceInRight { + 0% { + opacity: 0; + transform: translateX(2000px); + } + + 60% { + opacity: 1; + transform: translateX(-30px); + } + + 80% { + transform: translateX(10px); + } + + 100% { + transform: translateX(0); + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + -moz-animation-name: bounceInRight; + -o-animation-name: bounceInRight; + animation-name: bounceInRight; +} +@-webkit-keyframes bounceOut { + 0% { + -webkit-transform: scale(1); + } + + 25% { + -webkit-transform: scale(.95); + } + + 50% { + opacity: 1; + -webkit-transform: scale(1.1); + } + + 100% { + opacity: 0; + -webkit-transform: scale(.3); + } +} + +@-moz-keyframes bounceOut { + 0% { + -moz-transform: scale(1); + } + + 25% { + -moz-transform: scale(.95); + } + + 50% { + opacity: 1; + -moz-transform: scale(1.1); + } + + 100% { + opacity: 0; + -moz-transform: scale(.3); + } +} + +@-o-keyframes bounceOut { + 0% { + -o-transform: scale(1); + } + + 25% { + -o-transform: scale(.95); + } + + 50% { + opacity: 1; + -o-transform: scale(1.1); + } + + 100% { + opacity: 0; + -o-transform: scale(.3); + } +} + +@keyframes bounceOut { + 0% { + transform: scale(1); + } + + 25% { + transform: scale(.95); + } + + 50% { + opacity: 1; + transform: scale(1.1); + } + + 100% { + opacity: 0; + transform: scale(.3); + } +} + +.bounceOut { + -webkit-animation-name: bounceOut; + -moz-animation-name: bounceOut; + -o-animation-name: bounceOut; + animation-name: bounceOut; +} +@-webkit-keyframes bounceOutUp { + 0% { + -webkit-transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(-2000px); + } +} + +@-moz-keyframes bounceOutUp { + 0% { + -moz-transform: translateY(0); + } + + 20% { + opacity: 1; + -moz-transform: translateY(20px); + } + + 100% { + opacity: 0; + -moz-transform: translateY(-2000px); + } +} + +@-o-keyframes bounceOutUp { + 0% { + -o-transform: translateY(0); + } + + 20% { + opacity: 1; + -o-transform: translateY(20px); + } + + 100% { + opacity: 0; + -o-transform: translateY(-2000px); + } +} + +@keyframes bounceOutUp { + 0% { + transform: translateY(0); + } + + 20% { + opacity: 1; + transform: translateY(20px); + } + + 100% { + opacity: 0; + transform: translateY(-2000px); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + -moz-animation-name: bounceOutUp; + -o-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} +@-webkit-keyframes bounceOutDown { + 0% { + -webkit-transform: translateY(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateY(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateY(2000px); + } +} + +@-moz-keyframes bounceOutDown { + 0% { + -moz-transform: translateY(0); + } + + 20% { + opacity: 1; + -moz-transform: translateY(-20px); + } + + 100% { + opacity: 0; + -moz-transform: translateY(2000px); + } +} + +@-o-keyframes bounceOutDown { + 0% { + -o-transform: translateY(0); + } + + 20% { + opacity: 1; + -o-transform: translateY(-20px); + } + + 100% { + opacity: 0; + -o-transform: translateY(2000px); + } +} + +@keyframes bounceOutDown { + 0% { + transform: translateY(0); + } + + 20% { + opacity: 1; + transform: translateY(-20px); + } + + 100% { + opacity: 0; + transform: translateY(2000px); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + -moz-animation-name: bounceOutDown; + -o-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} +@-webkit-keyframes bounceOutLeft { + 0% { + -webkit-transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(-2000px); + } +} + +@-moz-keyframes bounceOutLeft { + 0% { + -moz-transform: translateX(0); + } + + 20% { + opacity: 1; + -moz-transform: translateX(20px); + } + + 100% { + opacity: 0; + -moz-transform: translateX(-2000px); + } +} + +@-o-keyframes bounceOutLeft { + 0% { + -o-transform: translateX(0); + } + + 20% { + opacity: 1; + -o-transform: translateX(20px); + } + + 100% { + opacity: 0; + -o-transform: translateX(-2000px); + } +} + +@keyframes bounceOutLeft { + 0% { + transform: translateX(0); + } + + 20% { + opacity: 1; + transform: translateX(20px); + } + + 100% { + opacity: 0; + transform: translateX(-2000px); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + -moz-animation-name: bounceOutLeft; + -o-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} +@-webkit-keyframes bounceOutRight { + 0% { + -webkit-transform: translateX(0); + } + + 20% { + opacity: 1; + -webkit-transform: translateX(-20px); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(2000px); + } +} + +@-moz-keyframes bounceOutRight { + 0% { + -moz-transform: translateX(0); + } + + 20% { + opacity: 1; + -moz-transform: translateX(-20px); + } + + 100% { + opacity: 0; + -moz-transform: translateX(2000px); + } +} + +@-o-keyframes bounceOutRight { + 0% { + -o-transform: translateX(0); + } + + 20% { + opacity: 1; + -o-transform: translateX(-20px); + } + + 100% { + opacity: 0; + -o-transform: translateX(2000px); + } +} + +@keyframes bounceOutRight { + 0% { + transform: translateX(0); + } + + 20% { + opacity: 1; + transform: translateX(-20px); + } + + 100% { + opacity: 0; + transform: translateX(2000px); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + -moz-animation-name: bounceOutRight; + -o-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} +@-webkit-keyframes rotateIn { + 0% { + -webkit-transform-origin: center center; + -webkit-transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: center center; + -webkit-transform: rotate(0); + opacity: 1; + } +} +@-moz-keyframes rotateIn { + 0% { + -moz-transform-origin: center center; + -moz-transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -moz-transform-origin: center center; + -moz-transform: rotate(0); + opacity: 1; + } +} +@-o-keyframes rotateIn { + 0% { + -o-transform-origin: center center; + -o-transform: rotate(-200deg); + opacity: 0; + } + + 100% { + -o-transform-origin: center center; + -o-transform: rotate(0); + opacity: 1; + } +} +@keyframes rotateIn { + 0% { + transform-origin: center center; + transform: rotate(-200deg); + opacity: 0; + } + + 100% { + transform-origin: center center; + transform: rotate(0); + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + -moz-animation-name: rotateIn; + -o-animation-name: rotateIn; + animation-name: rotateIn; +} +@-webkit-keyframes rotateInUpLeft { + 0% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(0); + opacity: 1; + } +} + +@-moz-keyframes rotateInUpLeft { + 0% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(90deg); + opacity: 0; + } + + 100% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(0); + opacity: 1; + } +} + +@-o-keyframes rotateInUpLeft { + 0% { + -o-transform-origin: left bottom; + -o-transform: rotate(90deg); + opacity: 0; + } + + 100% { + -o-transform-origin: left bottom; + -o-transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + 0% { + transform-origin: left bottom; + transform: rotate(90deg); + opacity: 0; + } + + 100% { + transform-origin: left bottom; + transform: rotate(0); + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + -moz-animation-name: rotateInUpLeft; + -o-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} +@-webkit-keyframes rotateInDownLeft { + 0% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(0); + opacity: 1; + } +} + +@-moz-keyframes rotateInDownLeft { + 0% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(0); + opacity: 1; + } +} + +@-o-keyframes rotateInDownLeft { + 0% { + -o-transform-origin: left bottom; + -o-transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -o-transform-origin: left bottom; + -o-transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + 0% { + transform-origin: left bottom; + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + transform-origin: left bottom; + transform: rotate(0); + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + -moz-animation-name: rotateInDownLeft; + -o-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} +@-webkit-keyframes rotateInUpRight { + 0% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(0); + opacity: 1; + } +} + +@-moz-keyframes rotateInUpRight { + 0% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(0); + opacity: 1; + } +} + +@-o-keyframes rotateInUpRight { + 0% { + -o-transform-origin: right bottom; + -o-transform: rotate(-90deg); + opacity: 0; + } + + 100% { + -o-transform-origin: right bottom; + -o-transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInUpRight { + 0% { + transform-origin: right bottom; + transform: rotate(-90deg); + opacity: 0; + } + + 100% { + transform-origin: right bottom; + transform: rotate(0); + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + -moz-animation-name: rotateInUpRight; + -o-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} +@-webkit-keyframes rotateInDownRight { + 0% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(90deg); + opacity: 0; + } + + 100% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(0); + opacity: 1; + } +} + +@-moz-keyframes rotateInDownRight { + 0% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(90deg); + opacity: 0; + } + + 100% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(0); + opacity: 1; + } +} + +@-o-keyframes rotateInDownRight { + 0% { + -o-transform-origin: right bottom; + -o-transform: rotate(90deg); + opacity: 0; + } + + 100% { + -o-transform-origin: right bottom; + -o-transform: rotate(0); + opacity: 1; + } +} + +@keyframes rotateInDownRight { + 0% { + transform-origin: right bottom; + transform: rotate(90deg); + opacity: 0; + } + + 100% { + transform-origin: right bottom; + transform: rotate(0); + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + -moz-animation-name: rotateInDownRight; + -o-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} +@-webkit-keyframes rotateOut { + 0% { + -webkit-transform-origin: center center; + -webkit-transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: center center; + -webkit-transform: rotate(200deg); + opacity: 0; + } +} + +@-moz-keyframes rotateOut { + 0% { + -moz-transform-origin: center center; + -moz-transform: rotate(0); + opacity: 1; + } + + 100% { + -moz-transform-origin: center center; + -moz-transform: rotate(200deg); + opacity: 0; + } +} + +@-o-keyframes rotateOut { + 0% { + -o-transform-origin: center center; + -o-transform: rotate(0); + opacity: 1; + } + + 100% { + -o-transform-origin: center center; + -o-transform: rotate(200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + 0% { + transform-origin: center center; + transform: rotate(0); + opacity: 1; + } + + 100% { + transform-origin: center center; + transform: rotate(200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + -moz-animation-name: rotateOut; + -o-animation-name: rotateOut; + animation-name: rotateOut; +} +@-webkit-keyframes rotateOutUpLeft { + 0% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(-90deg); + opacity: 0; + } +} + +@-moz-keyframes rotateOutUpLeft { + 0% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(0); + opacity: 1; + } + + 100% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(-90deg); + opacity: 0; + } +} + +@-o-keyframes rotateOutUpLeft { + 0% { + -o-transform-origin: left bottom; + -o-transform: rotate(0); + opacity: 1; + } + + 100% { + -o-transform-origin: left bottom; + -o-transform: rotate(-90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + 0% { + transform-origin: left bottom; + transform: rotate(0); + opacity: 1; + } + + 100% { + transform-origin: left bottom; + transform: rotate(-90deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + -moz-animation-name: rotateOutUpLeft; + -o-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} +@-webkit-keyframes rotateOutDownLeft { + 0% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: left bottom; + -webkit-transform: rotate(90deg); + opacity: 0; + } +} + +@-moz-keyframes rotateOutDownLeft { + 0% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(0); + opacity: 1; + } + + 100% { + -moz-transform-origin: left bottom; + -moz-transform: rotate(90deg); + opacity: 0; + } +} + +@-o-keyframes rotateOutDownLeft { + 0% { + -o-transform-origin: left bottom; + -o-transform: rotate(0); + opacity: 1; + } + + 100% { + -o-transform-origin: left bottom; + -o-transform: rotate(90deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + 0% { + transform-origin: left bottom; + transform: rotate(0); + opacity: 1; + } + + 100% { + transform-origin: left bottom; + transform: rotate(90deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + -moz-animation-name: rotateOutDownLeft; + -o-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} +@-webkit-keyframes rotateOutUpRight { + 0% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(90deg); + opacity: 0; + } +} + +@-moz-keyframes rotateOutUpRight { + 0% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(0); + opacity: 1; + } + + 100% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(90deg); + opacity: 0; + } +} + +@-o-keyframes rotateOutUpRight { + 0% { + -o-transform-origin: right bottom; + -o-transform: rotate(0); + opacity: 1; + } + + 100% { + -o-transform-origin: right bottom; + -o-transform: rotate(90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + 0% { + transform-origin: right bottom; + transform: rotate(0); + opacity: 1; + } + + 100% { + transform-origin: right bottom; + transform: rotate(90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + -moz-animation-name: rotateOutUpRight; + -o-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} +@-webkit-keyframes rotateOutDownRight { + 0% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(0); + opacity: 1; + } + + 100% { + -webkit-transform-origin: right bottom; + -webkit-transform: rotate(-90deg); + opacity: 0; + } +} + +@-moz-keyframes rotateOutDownRight { + 0% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(0); + opacity: 1; + } + + 100% { + -moz-transform-origin: right bottom; + -moz-transform: rotate(-90deg); + opacity: 0; + } +} + +@-o-keyframes rotateOutDownRight { + 0% { + -o-transform-origin: right bottom; + -o-transform: rotate(0); + opacity: 1; + } + + 100% { + -o-transform-origin: right bottom; + -o-transform: rotate(-90deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + 0% { + transform-origin: right bottom; + transform: rotate(0); + opacity: 1; + } + + 100% { + transform-origin: right bottom; + transform: rotate(-90deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + -moz-animation-name: rotateOutDownRight; + -o-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} +@-webkit-keyframes hinge { + 0% { -webkit-transform: rotate(0); -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; } + 20%, 60% { -webkit-transform: rotate(80deg); -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; } + 40% { -webkit-transform: rotate(60deg); -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; } + 80% { -webkit-transform: rotate(60deg) translateY(0); opacity: 1; -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; } + 100% { -webkit-transform: translateY(700px); opacity: 0; } +} + +@-moz-keyframes hinge { + 0% { -moz-transform: rotate(0); -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; } + 20%, 60% { -moz-transform: rotate(80deg); -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; } + 40% { -moz-transform: rotate(60deg); -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; } + 80% { -moz-transform: rotate(60deg) translateY(0); opacity: 1; -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; } + 100% { -moz-transform: translateY(700px); opacity: 0; } +} + +@-o-keyframes hinge { + 0% { -o-transform: rotate(0); -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; } + 20%, 60% { -o-transform: rotate(80deg); -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; } + 40% { -o-transform: rotate(60deg); -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; } + 80% { -o-transform: rotate(60deg) translateY(0); opacity: 1; -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; } + 100% { -o-transform: translateY(700px); opacity: 0; } +} + +@keyframes hinge { + 0% { transform: rotate(0); transform-origin: top left; animation-timing-function: ease-in-out; } + 20%, 60% { transform: rotate(80deg); transform-origin: top left; animation-timing-function: ease-in-out; } + 40% { transform: rotate(60deg); transform-origin: top left; animation-timing-function: ease-in-out; } + 80% { transform: rotate(60deg) translateY(0); opacity: 1; transform-origin: top left; animation-timing-function: ease-in-out; } + 100% { transform: translateY(700px); opacity: 0; } +} + +.hinge { + -webkit-animation-name: hinge; + -moz-animation-name: hinge; + -o-animation-name: hinge; + animation-name: hinge; +} +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + 0% { opacity: 0; -webkit-transform: translateX(-100%) rotate(-120deg); } + 100% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); } +} + +@-moz-keyframes rollIn { + 0% { opacity: 0; -moz-transform: translateX(-100%) rotate(-120deg); } + 100% { opacity: 1; -moz-transform: translateX(0px) rotate(0deg); } +} + +@-o-keyframes rollIn { + 0% { opacity: 0; -o-transform: translateX(-100%) rotate(-120deg); } + 100% { opacity: 1; -o-transform: translateX(0px) rotate(0deg); } +} + +@keyframes rollIn { + 0% { opacity: 0; transform: translateX(-100%) rotate(-120deg); } + 100% { opacity: 1; transform: translateX(0px) rotate(0deg); } +} + +.rollIn { + -webkit-animation-name: rollIn; + -moz-animation-name: rollIn; + -o-animation-name: rollIn; + animation-name: rollIn; +} +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + 0% { + opacity: 1; + -webkit-transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -webkit-transform: translateX(100%) rotate(120deg); + } +} + +@-moz-keyframes rollOut { + 0% { + opacity: 1; + -moz-transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -moz-transform: translateX(100%) rotate(120deg); + } +} + +@-o-keyframes rollOut { + 0% { + opacity: 1; + -o-transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + -o-transform: translateX(100%) rotate(120deg); + } +} + +@keyframes rollOut { + 0% { + opacity: 1; + transform: translateX(0px) rotate(0deg); + } + + 100% { + opacity: 0; + transform: translateX(100%) rotate(120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + -moz-animation-name: rollOut; + -o-animation-name: rollOut; + animation-name: rollOut; +} + +/* originally authored by Angelo Rohit - https://github.com/angelorohit */ + +@-webkit-keyframes lightSpeedIn { + 0% { -webkit-transform: translateX(100%) skewX(-30deg); opacity: 0; } + 60% { -webkit-transform: translateX(-20%) skewX(30deg); opacity: 1; } + 80% { -webkit-transform: translateX(0%) skewX(-15deg); opacity: 1; } + 100% { -webkit-transform: translateX(0%) skewX(0deg); opacity: 1; } +} + +@-moz-keyframes lightSpeedIn { + 0% { -moz-transform: translateX(100%) skewX(-30deg); opacity: 0; } + 60% { -moz-transform: translateX(-20%) skewX(30deg); opacity: 1; } + 80% { -moz-transform: translateX(0%) skewX(-15deg); opacity: 1; } + 100% { -moz-transform: translateX(0%) skewX(0deg); opacity: 1; } +} + +@-o-keyframes lightSpeedIn { + 0% { -o-transform: translateX(100%) skewX(-30deg); opacity: 0; } + 60% { -o-transform: translateX(-20%) skewX(30deg); opacity: 1; } + 80% { -o-transform: translateX(0%) skewX(-15deg); opacity: 1; } + 100% { -o-transform: translateX(0%) skewX(0deg); opacity: 1; } +} + +@keyframes lightSpeedIn { + 0% { transform: translateX(100%) skewX(-30deg); opacity: 0; } + 60% { transform: translateX(-20%) skewX(30deg); opacity: 1; } + 80% { transform: translateX(0%) skewX(-15deg); opacity: 1; } + 100% { transform: translateX(0%) skewX(0deg); opacity: 1; } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + -moz-animation-name: lightSpeedIn; + -o-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + + -webkit-animation-timing-function: ease-out; + -moz-animation-timing-function: ease-out; + -o-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +.animated.lightSpeedIn { + -webkit-animation-duration: 0.5s; + -moz-animation-duration: 0.5s; + -o-animation-duration: 0.5s; + animation-duration: 0.5s; +} + +/* originally authored by Angelo Rohit - https://github.com/angelorohit */ + +@-webkit-keyframes lightSpeedOut { + 0% { -webkit-transform: translateX(0%) skewX(0deg); opacity: 1; } + 100% { -webkit-transform: translateX(100%) skewX(-30deg); opacity: 0; } +} + +@-moz-keyframes lightSpeedOut { + 0% { -moz-transform: translateX(0%) skewX(0deg); opacity: 1; } + 100% { -moz-transform: translateX(100%) skewX(-30deg); opacity: 0; } +} + +@-o-keyframes lightSpeedOut { + 0% { -o-transform: translateX(0%) skewX(0deg); opacity: 1; } + 100% { -o-transform: translateX(100%) skewX(-30deg); opacity: 0; } +} + +@keyframes lightSpeedOut { + 0% { transform: translateX(0%) skewX(0deg); opacity: 1; } + 100% { transform: translateX(100%) skewX(-30deg); opacity: 0; } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + -moz-animation-name: lightSpeedOut; + -o-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + + -webkit-animation-timing-function: ease-in; + -moz-animation-timing-function: ease-in; + -o-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +.animated.lightSpeedOut { + -webkit-animation-duration: 0.25s; + -moz-animation-duration: 0.25s; + -o-animation-duration: 0.25s; + animation-duration: 0.25s; +} + +/* originally authored by Angelo Rohit - https://github.com/angelorohit */ + +@-webkit-keyframes wiggle { + 0% { -webkit-transform: skewX(9deg); } + 10% { -webkit-transform: skewX(-8deg); } + 20% { -webkit-transform: skewX(7deg); } + 30% { -webkit-transform: skewX(-6deg); } + 40% { -webkit-transform: skewX(5deg); } + 50% { -webkit-transform: skewX(-4deg); } + 60% { -webkit-transform: skewX(3deg); } + 70% { -webkit-transform: skewX(-2deg); } + 80% { -webkit-transform: skewX(1deg); } + 90% { -webkit-transform: skewX(0deg); } + 100% { -webkit-transform: skewX(0deg); } +} + +@-moz-keyframes wiggle { + 0% { -moz-transform: skewX(9deg); } + 10% { -moz-transform: skewX(-8deg); } + 20% { -moz-transform: skewX(7deg); } + 30% { -moz-transform: skewX(-6deg); } + 40% { -moz-transform: skewX(5deg); } + 50% { -moz-transform: skewX(-4deg); } + 60% { -moz-transform: skewX(3deg); } + 70% { -moz-transform: skewX(-2deg); } + 80% { -moz-transform: skewX(1deg); } + 90% { -moz-transform: skewX(0deg); } + 100% { -moz-transform: skewX(0deg); } +} + +@-o-keyframes wiggle { + 0% { -o-transform: skewX(9deg); } + 10% { -o-transform: skewX(-8deg); } + 20% { -o-transform: skewX(7deg); } + 30% { -o-transform: skewX(-6deg); } + 40% { -o-transform: skewX(5deg); } + 50% { -o-transform: skewX(-4deg); } + 60% { -o-transform: skewX(3deg); } + 70% { -o-transform: skewX(-2deg); } + 80% { -o-transform: skewX(1deg); } + 90% { -o-transform: skewX(0deg); } + 100% { -o-transform: skewX(0deg); } +} + +@keyframes wiggle { + 0% { transform: skewX(9deg); } + 10% { transform: skewX(-8deg); } + 20% { transform: skewX(7deg); } + 30% { transform: skewX(-6deg); } + 40% { transform: skewX(5deg); } + 50% { transform: skewX(-4deg); } + 60% { transform: skewX(3deg); } + 70% { transform: skewX(-2deg); } + 80% { transform: skewX(1deg); } + 90% { transform: skewX(0deg); } + 100% { transform: skewX(0deg); } +} + +.wiggle { + -webkit-animation-name: wiggle; + -moz-animation-name: wiggle; + -o-animation-name: wiggle; + animation-name: wiggle; + + -webkit-animation-timing-function: ease-in; + -moz-animation-timing-function: ease-in; + -o-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +.animated.wiggle { + -webkit-animation-duration: 0.75s; + -moz-animation-duration: 0.75s; + -o-animation-duration: 0.75s; + animation-duration: 0.75s; +} diff --git a/cas-server-webapp/src/main/webapp/assets/css/custom.css b/cas-server-webapp/src/main/webapp/assets/css/custom.css new file mode 100644 index 0000000..3c81259 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/custom.css @@ -0,0 +1 @@ +/* here you can put your own css to customize and override the theme */ diff --git a/cas-server-webapp/src/main/webapp/assets/css/pages/login-soft.css b/cas-server-webapp/src/main/webapp/assets/css/pages/login-soft.css new file mode 100644 index 0000000..4c8773e --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/pages/login-soft.css @@ -0,0 +1,168 @@ +/*** +Login page +***/ + +/* logo page */ +.login { + background-color: #666 !important; +} + +.login .logo { + margin: 0 auto; + margin-top:60px; + padding: 15px; + text-align: center; +} + + +.login .content { + background: url(../../img/bg-white-lock.png) repeat; + width: 360px; + margin: 0 auto; + margin-bottom: 0px; + padding: 30px; + padding-top: 20px; + padding-bottom: 15px; +} + +.login .content h3 { + color: #eee; +} +.login .content h4 { + color: #eee; +} + +.login .content p, +.login .content label { + color: #fff; +} + +.login .content .login-form, +.login .content .forget-form { + padding: 0px; + margin: 0px; +} + +.login .content .form-control { + background-color: #fff; +} + +.login .content .forget-form { + display: none; +} + +.login .content .register-form { + display: none; +} + +.login .content .form-title { + font-weight: 300; + margin-bottom: 25px; +} + +.login .content .form-actions { + background-color: transparent; + clear: both; + border: 0px; + padding: 0px 30px 25px 30px; + margin-left: -30px; + margin-right: -30px; +} + +.login .content .form-actions .checkbox { + margin-left: 0; + padding-left: 0; +} + +.login .content .forget-form .form-actions { + border: 0; + margin-bottom: 0; + padding-bottom: 20px; +} + +.login .content .register-form .form-actions { + border: 0; + margin-bottom: 0; + padding-bottom: 0px; +} + +.login .content .form-actions .checkbox { + margin-top: 8px; + display: inline-block; +} + +.login .content .form-actions .btn { + margin-top: 1px; +} + +.login .content .forget-password { + margin-top: 25px; +} + +.login .content .create-account { + border-top: 1px dotted #eee; + padding-top: 10px; + margin-top: 15px; +} + +.login .content .create-account a { + display: inline-block; + margin-top: 5px; +} + +/* select2 dropdowns */ +.login .content .select2-container i { + display: inline-block; + position: relative; + color: #ccc; + z-index: 1; + top:1px; + margin: 4px 4px 0px 3px; + width: 16px; + height: 16px; + font-size: 16px; + text-align: center; +} + +.login .content .has-error .select2-container i { + color: #b94a48; +} + +.login .content .select2-container a span { + font-size: 13px; +} + +.login .content .select2-container a span img { + margin-left: 4px; +} + +/* footer copyright */ +.login .copyright { + text-align: center; + margin: 0 auto; + padding: 10px; + color: #eee; + font-size: 13px; +} + +@media (max-width: 480px) { + /*** + Login page + ***/ + .login .logo { + margin-top:10px; + } + + .login .content { + padding: 30px; + width: 222px; + } + + .login .content h3 { + font-size: 22px; + } + + .login .checkbox { + font-size: 13px; + } +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/css/plugins.css b/cas-server-webapp/src/main/webapp/assets/css/plugins.css new file mode 100644 index 0000000..560e0ce --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/plugins.css @@ -0,0 +1,1534 @@ +/************************** + PLUGIN CSS CUSTOMIZATIONS +**************************/ + +/*** +Calendar with full calendar +***/ +.external-event { + display: inline-block ; + cursor:move; + margin-bottom: 5px ; + margin-left: 5px ; +} + +.portlet .event-form-title { + font-size: 14px; + margin-top: 4px; + font-weight: 400; + margin-bottom: 10px; +} + +.portlet.calendar .fc-button { + -webkit-box-shadow: none ; + -moz-box-shadow: none ; + box-shadow: none ; + text-shadow: none; + border: 0 ; + padding: 6px 8px 30px 8px ; + margin-left:2px; + border-top-style: none; + border-bottom-style: none; + border-right-style: solid; + border-left-style: solid; + border-color: #ddd; + background: transparent; + color: #fff; + top: -46px; +} + +.portlet.calendar .fc-header { + margin-bottom:-21px; +} + +.portlet.calendar .fc-button-prev { + padding-right: 10px; + padding-left: 8px; +} + +.portlet.calendar .fc-button-next { + padding-right: 8px; + padding-left: 10px; +} + +.portlet.calendar .fc-button.fc-state-active, +.portlet.calendar .fc-button.fc-state-hover { + color: #666 ; + background-color: #F9F9F9 ; +} + +.portlet.calendar .fc-button.fc-state-disabled { + color: #ddd ; +} + +.portlet.calendar .fc-text-arrow { + font-size: 22px; + font-family: "Courier New", Courier, monospace; + vertical-align: baseline; +} + +/* begin: event element */ +.portlet.calendar .fc-event { + border: 0px; + background-color: #69a4e0; + color: #fff; +} + +.portlet.calendar .fc-event-inner { + border: 0px; +} + +.portlet.calendar .fc-event-time { + float: left; + text-align: left; + color: #fff; + font-size: 13px; + font-weight: 300; +} + +.portlet.calendar .fc-event-title { + text-align: left; + float: left; + color: #fff; + font-size: 13px; + font-weight: 300; +} +/* end: event element */ + +.portlet.calendar .fc-header-title h2 { + font-size: 14px ; + line-height: 20px; + font-weight: 400; + color: #111; +} + +.portlet.calendar .fc-widget-header { + background-image: none ; + filter:none; + background-color: #eee ; + text-transform: uppercase; + font-weight: 300; +} + +.portlet.calendar .mobile .fc-button { + margin-left: 2px ; +} + +.portlet.calendar .mobile .fc-button { + padding: 0px 6px 20px 6px ; + margin-left:2px ; + border: 0; + background-color: #ddd ; + background-image: none; + -webkit-box-shadow: none ; + -moz-box-shadow: none ; + box-shadow: none ; + -webkit-border-radius: 0 ; + -moz-border-radius: 0 ; + border-radius: 0 ; + color: #000; + text-shadow: none ; + text-align: center; +} + +.portlet.calendar .mobile .fc-state-hover, +.portlet.calendar .mobile .fc-state-active { + background-color: #eee ; +} + +.portlet.calendar .mobile .fc-button-prev { + margin-right: 5px; + margin-top: -2px; +} + +.portlet.calendar .mobile .fc-button-next { + margin-right: -0px; + margin-top: -2px; +} + +.portlet.calendar .mobile .fc-header-space { + margin: 0px ; + padding: 0px ; + width: 0px ; +} + + .portlet.calendar .mobile .fc-state-disabled { + color: #bbb ; + } + + .portlet.calendar .mobile .fc-header-left { + position: absolute; + z-index: 10; + } + + .portlet.calendar .mobile .fc-header-right { + position: absolute; + z-index: 9; + } + + .portlet.calendar .mobile .fc-header-left .fc-button { + top: -2px ; + } + + .portlet.calendar .mobile .fc-header-right { + position: relative; + right:0; + } + + .portlet.calendar .mobile .fc-header-right .fc-button { + top: 35px ; + } + + .portlet.calendar .mobile .fc-content { + margin-top: 53px; + } + + +/*** +Form wizard +***/ + +.form-wizard .progress { + margin-bottom: 30px; +} + +.form-wizard .steps { + padding: 10px 0; + margin-bottom: 15px; +} + +.form-wizard .steps { + background-color: #fff ; + background-image: none ; + filter:none ; + border: 0px; + box-shadow: none ; +} + +.form-wizard .steps li a { + background-color: #fff ; + background-image: none ; + filter:none; + border: 0px; + box-shadow: none ; +} + +.form-wizard .steps li a:hover { + background: none; +} + +.form-wizard .step:hover { + text-decoration: none; +} + +.form-wizard .step .number { + background-color: #eee; + display: inline-block; + text-align: center !important; + font-size: 16px; + font-weight: 300; + padding: 11px 15px 13px 15px; + margin-right: 10px; + height: 45px; + width: 45px; + -webkit-border-radius: 50% !important; + -moz-border-radius: 50% !important; + border-radius: 50% !important; +} + +.form-wizard .step .desc { + display: inline-block; + font-size: 16px; + font-weight: 300; +} + +.form-wizard .active .step .number { + background-color: #35aa47; + color: #fff; +} + +.form-wizard .active .step .desc { + color: #333; + font-weight: 400; +} + +.form-wizard .step i { + display: none; +} + +.form-wizard .done .step .number { + background-color: #f2ae43; + color: #fff; +} + +.form-wizard .done .step .desc { + font-weight: 400; +} + +.form-wizard .done .step i { + font-size: 12px; + font-weight: normal; + color: #999; + display: inline-block; +} + + +@media (min-width: 768px) and (max-width: 1280px) { + .form-wizard .step .desc { + margin-top: 10px; + display: block; + } +} + +@media (max-width: 768px) { + .form-wizard .steps > li > a { + text-align: left; + } +} + +/*** +Google Maps +***/ +.gmaps { + height: 300px; + width: 100%; +} + +/* important! bootstrap sets max-width on img to 100% which conflicts with google map canvas*/ +.gmaps img { + max-width: none; +} + +#gmap_static div{ + background-repeat: no-repeat ; + background-position: 50% 50% ; + height:100%; + display:block; + height: 300px; +} + +#gmap_routes_instructions { + margin-top: 10px; + margin-bottom: 0px; +} + +/*** +SlimScrollBar plugins css changes +***/ +.scroller { + padding: 0px ; + margin: 0px ; + padding-right: 12px ; + overflow: hidden; +} + +.scroller-footer { + margin-top: 10px; +} + +.scroller-footer:after, +.scroller-footer:before { + content: ""; + display: table; + line-height: 0; +} + +.scroller-footer:after { + clear: both; +} + +.portlet-body .slimScrollBar { + margin-right: 0px ; +} + +/*** +jqvmap changes +***/ +.jqvmap-zoomin { + height: 16px; + width: 16px; + background-color: #666 ; +} + +.jqvmap-zoomout { + height: 16px; + width: 16px; + background-color: #666 ; +} + +.vmaps { + position: relative; + overflow: hidden; + height: 300px; +} + + +/*** +Error state for WYSIWYG Editors +***/ +.has-error .md-editor, +.has-error .wysihtml5-sandbox, +.has-error .cke { + border: 1px solid #B94A48 !important; +} + +.has-success .md-editor, +.has-success .wysihtml5-sandbox, +.has-success .cke { + border: 1px solid #468847 !important; +} + +/*** +Select2 plugin css changes +***/ + +/* enable form validation classes for select2 dropdowns */ +.has-error .select2-container .select2-choice { + border-color: #B94A48; +} + +.has-error .select2-container.select2-dropdown-open .select2-choice { + border-color: #e5e5e5; +} + +.has-error .select2-container.select2-dropdown-open .select2-choice > span { + color: #999999; +} + +.has-success .select2-container .select2-choice { + border-color: #468847; +} + +.has-success .select2-container.select2-dropdown-open .select2-choice { + border-color: #e5e5e5; +} + +.has-success .select2-container.select2-dropdown-open .select2-choice > span { + color: #999999; +} + + +/*** +Jansy File Input plugin css changes +***/ +.fileinput { + margin-bottom: 0; +} + + +/*** +WYSIWYG +***/ +.wysihtml5-toolbar li { + margin: 0px; + height: 29px; +} + +.wysihtml5-toolbar li .dropdown-menu { + margin-top: 5px; +} + +/*** +CKEditor css changes +***/ +.cke_bottom, +.cke_inner, +.cke_top, +.cke_reset, +.cke_dialog_title, +.cke_dialog_footer, +.cke_dialog { + background-image: none !important; + filter:none ; + border-top: 0 ; + border-bottom: 0 ; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; + text-shadow:none ; +} + +.cke_dialog_ui_button, +.cke_dialog_tab { + background-image: none !important; + filter:none ; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; + text-shadow:none !important; +} + +.cke_dialog_ui_button:hover, +.cke_dialog_tab:hover { + text-decoration: none; + text-shadow:none ; +} + +.cke_dialog_ui_input_text { + background-image: none !important; + filter:none ; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.cke_combo_button, +.cke_button, +.cke_toolbar, +.cke_toolgroup { + background-image: none !important; + filter:none !important; + border: 0 ; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.cke_button, +.cke_combo_button, +.cke_panel_grouptitle, +.cke_hc.cke_panel_listItem a { + background-image: none !important; + filter:none ; + text-shadow:none ; + -webkit-border-radius: 0px !important; + -moz-border-radius: 0px !important; + -ms-border-radius: 0px !important; + -o-border-radius: 0px !important; +} + +.cke_button:hover, +.cke_combo_button:hover { + background-color: #ddd; +} + +.cke_toolbar_break { + background-image: none !important; + filter:none !important; + border: 0 ; + box-shadow: none !important; + -webkit-box-shadow : none !important; + -moz-box-shadow: none !important; + -ms-box-shadow: none !important; + -o-box-shadow: none !important; +} + +/*** +Modify tags input plugin css +***/ +div.tagsinput { + min-height: 35px; + height: auto !important; + margin: 0; + padding: 5px 5px 0px 5px; + overflow: auto; +} + +div.tagsinput span.tag { + background: #aaa ; + color: #fff ; + border: 0 ; + padding: 3px 6px; + margin-top: 0; + margin-bottom: 5px; +} + +div.tagsinput input { + padding: 3px 6px ; + width: 75px !important; +} + +div.tagsinput span.tag a { + color: #fff ; +} + +div.tagsinput .not_valid { + color: #fff ; + padding: 3px 6px ; + background-color: #e02222 ; +} + +/*** +Gritter notification modify +***/ + +#gritter-notice-wrapper { + right:1px !important; +} + +.gritter-close { + left:auto !important; + right: 3px !important; +} + +.gritter-title { + font-family: 'Open Sans' ; + font-size: 18px ; + font-weight: 300 ; +} + +/*** +jQuery UI Sliders(new in v1.1.1) +***/ +.slider { + border: 0; + padding: 0; + display: block; + margin: 12px 5px; + min-height: 11px; +} + +.ui-slider-vertical { + width: 11px; +} + +.ui-slider-horizontal .ui-slider-handle { + top: -3px; +} + +.ui-slider-vertical .ui-slider-handle { + left: -3px; +} + +.ui-slider-vertical, +.ui-slider-handle { + filter: none !important; + background-image: none !important; +} + +/*** +Dropzone css changes(new in v1.1.1) +***/ +.dropzone { + -webkit-border-radius: 0px ; + -moz-border-radius: 0px ; + border-radius: 0px ; +} + + +/*** +Dashboard Charts(new in v1.2.1) +***/ +.easy-pie-chart, +.sparkline-chart { + text-align: center; +} + +.sparkline-chart { + margin-top: 15px; + position:relative ; +} + +.easy-pie-chart .number { + font-size: 16px; + font-weight: 300; + width: 85px; + margin: 0 auto; +} + +.sparkline-chart .number { + width: 100px; + margin: 0 auto; + margin-bottom: 10px; +} + +.sparkline-chart .title, +.easy-pie-chart .title { + display: block; + text-align: center; + color: #333; + font-weight: 300; + font-size: 16px; + margin-top: 5px; + margin-bottom: 10px; +} + +.sparkline-chart .title:hover, +.easy-pie-chart .title:hover { + color: #666; + text-decoration: none; +} + +.sparkline-chart .title > i, +.easy-pie-chart .title > i { + margin-top: 5px; +} + +/*** +Fancy box fix overlay fix(in v1.2.4) +***/ +.fancybox-overlay { + z-index: 10000 ; +} + +/*** +Datatables Plugin(in v1.3) +***/ +.dataTable { + width: 100% !important; + clear: both; + margin-top: 5px; +} + +.dataTables_filter label { + line-height: 32px ; +} + +.dataTable .row-details { + margin-top: 3px; + display: inline-block; + cursor: pointer; + width: 14px; + height: 14px; +} + +.dataTable .row-details.row-details-close { + background: url("../img/datatable-row-openclose.png") no-repeat 0 0; +} + +.dataTable .row-details.row-details-open { + background: url("../img/datatable-row-openclose.png") no-repeat 0 -23px ; +} + +.dataTable .details { + background-color: #eee ; +} + +.dataTable .details td, +.dataTable .details th { + padding: 4px; + background: none ; + border: 0; +} + +.dataTable .details tr:hover td, +.dataTable .details tr:hover th { + background: none ; +} + +.dataTable .details tr:nth-child(odd) td, +.dataTable .details tr:nth-child(odd) th { + background-color: #eee ; +} + +.dataTable .details tr:nth-child(even) td, +.dataTable .details tr:nth-child(even) th { + background-color: #eee ; +} + +.dataTable > thead > tr > th.sorting, +.dataTable > thead > tr > th.sorting_asc, +.dataTable > thead > tr > th.sorting_desc { + padding-right: 18px; +} + +.dataTable .table-checkbox { + width: 8px !important; +} + +@media (max-width: 768px) { + .dataTables_wrapper .dataTables_length .form-control, + .dataTables_wrapper .dataTables_filter .form-control { + display: inline-block; + } + + .dataTables_wrapper .dataTables_info { + top: 17px; + } + + .dataTables_wrapper .dataTables_paginate { + margin-top: -15px; + } +} + +@media (max-width: 480px) { + .dataTables_wrapper .dataTables_filter .form-control { + width: 175px !important; + } + + .dataTables_wrapper .dataTables_paginate { + float: left; + margin-top: 20px; + } +} + +.dataTables_processing { + position: fixed; + top: 50%; + left: 50%; + min-width: 125px; + margin-left: 0; + padding: 7px; + text-align: center; + color: #333; + font-size: 13px; + border: 1px solid #ddd; + background-color: #eee; + vertical-align: middle; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); +} + +.dataTables_processing span { + line-height:15px; + vertical-align: middle; +} + +.dataTables_empty { + text-align: center; +} + +/*** +Extended Datatable +***/ + +.dataTables_extended_wrapper .seperator { + padding: 0 2px; +} + +.dataTables_extended_wrapper .dataTables_paginate, +.dataTables_extended_wrapper .dataTables_length, +.dataTables_extended_wrapper .dataTables_info { + display: inline-block; + float: none !important; + padding: 0 !important; + margin: 0 !important; + position: static !important; +} + +@media (max-width: 480px) { + + .dataTables_extended_wrapper .dataTables_paginate, + .dataTables_extended_wrapper .dataTables_length, + .dataTables_extended_wrapper .dataTables_info { + display: block; + margin-bottom: 10px !important; + } + + .dataTables_extended_wrapper .seperator { + display: none; + } +} + +.dataTables_extended_wrapper .dataTables_length label { + margin: 0 !important; + padding: 0 !important; + font-size: 13px; + float: none !important; + display: inline-block !important; +} + +.table-container .table-actions-wrapper { + display: none; +} + +/*** +Password Strength(in v1.4) +***/ +.password-strength .password-verdict { + display: inline-block; + margin-top: 6px; + margin-left: 5px; +} + +.password-strength .progress { + margin-top: 5px; + margin-bottom: 0; +} + +.password-strength .progress-bar { + padding: 2px; +} + +/*** +Uniform disabled checkbox, radio button fix(in v1.4) +***/ + +.table .uniform-inline { + padding: 0; + margin: 0; +} + +.checker { + margin-top: -2px !important; + margin-right: 2px !important; +} + +.checker input, +.radio input { + outline: none !important; +} + +div.checker.disabled span, +div.checker.disabled.active span{ + background-position: -152px -260px; +} + +div.checker.disabled:hover, +div.radio.disabled:hover { + cursor: not-allowed; +} + +div.radio, +div.checker { + margin-right: 0; + margin-left: 3px; +} + +/*** +jQuery Sparkline +***/ +.jqstooltip { + width: auto !important; + height: auto !important; +} + + +/*** +jQuery Multi Select +***/ + +.ms-container .ms-list { + border: 1px solid #e5e5e5; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + +} + +.ms-container .ms-optgroup-label{ + font-size: 14px; +} + +.ms-container .ms-selectable li.ms-elem-selectable, +.ms-container .ms-selection li.ms-elem-selection{ + font-size: 13px; +} + +.ms-container .ms-list.ms-focus { + border-color: #999999; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +.ms-container .ms-selectable li.ms-hover, +.ms-container .ms-selection li.ms-hover{ + color: #333; + background-color: #eee; +} + +.ms-container .form-control { + margin-bottom: 5px; +} + +/*** +Bootstrap Colorpicker +***/ +.input-group.color .input-group-btn i { + position: absolute; + display: block; + cursor: pointer; + width: 20px; + height: 20px; + right: 6px; +} + +.colorpicker.dropdown-menu { + padding: 5px; +} + +/* change z-index when opened in modal */ +.modal-open .colorpicker { + z-index: 10055 !important; +} + +/*** +Bootstrap Datetimepicker +***/ + +.datetimepicker table td { + font-weight: 300 !important; + font-family: 'Open Sans' !important; +} + +.datetimepicker table th { + font-family: 'Open Sans' !important; + font-weight: 400 !important; +} + +.datetimepicker.dropdown-menu { + padding: 5px; +} + +.datetimepicker .active { + background-color:#4b8df8 !important; + background-image: none !important; + filter: none !important; +} + +.datetimepicker .active:hover { + background-color: #2678FC !important; + background-image: none !important; + filter: none !important; + +} + +/* change z-index when opened in modal */ +.modal-open .datetimepicker { + z-index: 10055 !important; +} + +/*** +Bootstrap Time Picker +***/ +.bootstrap-timepicker-widget table td a { + padding: 4px 0; +} + +.bootstrap-timepicker-widget input, +.bootstrap-timepicker-widget input:focus { + outline: none !important; + border: 0; +} + +.modal-open .bootstrap-timepicker-widget { + z-index: 10055 !important; +} + +.bootstrap-timepicker-widget.timepicker-orient-bottom:before, +.bootstrap-timepicker-widget.timepicker-orient-bottom:after { + top: auto; +} + +/*** +Bootstrap Datepicker +***/ + +.datepicker.dropdown-menu { + padding: 5px; +} + +.datepicker .selected { + background-color:#909090 !important; + background-image: none !important; + filter: none !important; +} + +.datepicker .active { + background-color:#4b8df8 !important; + background-image: none !important; + filter: none !important; +} + +.datepicker .active:hover { + background-color: #2678FC !important; + background-image: none !important; + filter: none !important; +} + +.datepicker .input-daterange input { + text-align: left; +} + +/* change z-index when opened in modal */ +.modal-open .datepicker { + z-index: 10055 !important; +} + +.datepicker table td { + font-weight: 300 !important; + font-family: 'Open Sans' !important; +} + +.datepicker table th { + font-family: 'Open Sans' !important; + font-weight: 400 !important; +} + + +/*** +Clockface +***/ + +.modal-open .clockface { + z-index: 10055 !important; +} + +.clockface .cell .inner.active, +.clockface .cell .outer.active { + background-color:#4b8df8 !important; + background-image: none ; + filter:none ; +} + + +/*** +Bootstrap Daterangepicker +***/ + +.modal-open .daterangepicker { + z-index: 10055 !important; +} + +.daterangepicker td { + text-shadow: none ; +} + +.daterangepicker td.active { + background-color: #4b8df8 ; + background-image: none ; + filter:none ; +} + +.daterangepicker th { + font-weight: 400; + font-size: 14px; +} + +.daterangepicker .ranges input[type="text"] { + width: 70px !important; + font-size: 11px; + vertical-align: middle; +} + +.daterangepicker .ranges label { + font-weight: 300; + display: block; +} + +.daterangepicker .ranges .btn { + margin-top: 10px; +} + +.daterangepicker.dropdown-menu { + padding: 5px; +} + +.daterangepicker .ranges li { + color: #333; +} + +.daterangepicker .ranges li.active, +.daterangepicker .ranges li:hover { + background: #4b8df8 !important; + border: 1px solid #4b8df8 !important; + color: #fff; +} + +.daterangepicker .range_inputs input { + margin-bottom: 0 !important; +} + +/*** +Bootstrap Editable +***/ + +.editable-input table, +.editable-input table th, +.editable-input table td, +.editable-input table tr { + border: 0 !important; +} + +.editable-input .combodate select { + margin-bottom: 5px; +} + +/*** +FuelUX Spinners +***/ + +.spinner-buttons.btn-group-vertical .btn { + text-align: center; + margin: 0; + height: 17px; + width: 22px; + padding-left: 6px; + padding-right: 6px; + padding-top: 0px; +} + + +/*** +NoUI Range Sliders +***/ +.noUi-handle { + height: 20px; + width: 20px; + margin: -3px 0 0 -20px; +} + +.noUi-base { + height: 16px; +} + +.noUi-connect { + background: #ffb848; +} + +/*** +Toastr Notifications +***/ +.toast { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.toast { + background-color: #030303; +} +.toast-success { + background-color: #51a351; +} +.toast-error { + background-color: #bd362f; +} +.toast-info { + background-color: #2f96b4; +} +.toast-warning { + background-color: #f89406; +} + +.toast .toast-close-button { + display: inline-block; + margin-top: 0px; + margin-right: 0px; + text-indent: -100000px; + width: 11px; + height: 16px; + background-repeat: no-repeat !important; + background-image: url("../img/portlet-remove-icon-white.png") !important; +} + +.toast-top-center { + top: 12px; + margin: 0 auto; + left: 50%; + margin-left: -150px; +} + +.toast-bottom-center { + bottom: 12px; + margin: 0 auto; + left: 50%; + margin-left: -150px; +} + +/*** +Google reCaptcha +***/ +.form-recaptcha-img { + margin-bottom: 10px; + clear: both; + border: 1px solid #e5e5e5; + padding: 5px; +} + +iframe[src="about:blank"] { + display:none; +} + +/*** +Bootstrap Markdown +***/ +.md-input { + padding: 5px !important; + border-bottom: 0 !important; +} + +.md-editor .btn-toolbar { + margin-left: 0px; +} + +.md-editor.active { + border: 1px solid #999999; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +/*** +Bootstrap Datepaginator +***/ +.datepaginator a { + font-family: 'Open Sans'; + font-size: 13px; + font-weight: 300; +} + +.datepicker .today { + background-image: none !important; + filter: none !important; +} + +#dp-calendar { + right: 4px !important; +} + +/*** +Font Awesome 4.0 Demo +***/ +.fa-item { + font-size: 14px; + padding: 10px 10px 10px 20px; +} + +.fa-item i { + font-size: 16px; + display: inline-block; + width: 20px; +} + +.fa-item:hover { + cursor: pointer; + background: #eee; +} + +/*** +Bootstrap Modal +***/ +/* fix: content shifting to the right on modal open */ +.modal-open.page-overflow .page-container, +.modal-open.page-overflow .page-container .navbar-fixed-top, +.modal-open.page-overflow .page-container .navbar-fixed-bottom, +.modal-open.page-overflow .modal-scrollable { + overflow-y: auto !important; +} + +.modal-scrollable { + overflow: hidden !important; +} + + +/*** +jQuery Notific8 Plugin +***/ + +.jquery-notific8-message { + font-size: 13px; +} + +[class*="jquery-notific8"], +[class*="jquery-notific8"]:after, +[class*="jquery-notific8"]:before { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +.right .jquery-notific8-close-sticky span, +.left .jquery-notific8-close-sticky span { + font-size: 10px; +} + +.jquery-notific8-heading { + font-weight: 300; + font-size: 16px; +} + +/*** +jQuery File Upload +***/ + +.blueimp-gallery .close { + background-image: url("../img/portlet-remove-icon-white.png") !important; + margin-top: -2px; +} + +.blueimp-gallery .prev, +.blueimp-gallery .next { + border-radius: 23px !important; +} + +/*** +Bootstrap Switch +***/ + +.has-switch { + border-color: #e5e5e5; +} + +.has-switch:focus { + -webkit-box-shadow: none; + box-shadow: none; +} + +/*** +Jstree +***/ + +.jstree-default .jstree-clicked { + border: 0; + background-color: #e1e1e1; + box-shadow:none; +} + +.jstree-default .jstree-hovered { + border: 0; + background-color: #eee; + box-shadow:none; +} + +.jstree-default .jstree-wholerow-clicked, +.jstree-wholerow .jstree-wholerow-clicked { + background: none; + border: 0; + background-color: #e1e1e1; + box-shadow:none; +} + +.jstree-default .jstree-wholerow-hovered, +.jstree-wholerow .jstree-wholerow-hovered { + border: 0; + background-color: #eee; + box-shadow:none; +} + +.jstree-icon.icon-lg { + margin-top: 1px; +} + +.jstree-open > .jstree-anchor > .fa-folder:before { + margin-left: 2px; + content: "\f07c"; +} + +.jstree-default.jstree-rtl .jstree-last { + background: transparent; + background-repeat: no-repeat; +} + +.vakata-context, +.vakata-context ul { + padding: 0; + min-width: 125px; + background-color: #ffffff; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + font-size: 14px; + font-family: "Segoe UI",Helvetica, Arial, sans-serif; + border: 1px solid #ddd; +} + +.vakata-context li a { + padding: 0 10px; +} + +.vakata-context .vakata-context-hover > a, +.vakata-context li a:hover { + background-color: #eee; + color: #333; + box-shadow: none; +} + +.vakata-context li a span, +.vakata-context li a ins { + display: none; +} + +.vakata-context .vakata-context-separator a, +.vakata-context-rtl .vakata-context-separator a { + margin: 0; +} + +.jstree-rename-input { + background-color: #ffffff !important; + border: 1px solid #e5e5e5 !important; + outline: none !important; + padding: 2px 6px !important; + margin-right: -4px !important; +} + +/*** +Bootstrap Select +***/ + +.bootstrap-select .btn { + border-color: #e5e5e5; +} + +.bootstrap-select.open .btn1 { + border-color: #999999; +} + +.bootstrap-select.open.dropup .btn1 { + border-color: #999999; +} + +.bootstrap-select .btn:focus { + outline: none !important; + outline-offset: 0; +} + +.bootstrap-select.btn-group .dropdown-menu { + margin-top: 1px; +} + +.bootstrap-select.btn-group .dropdown-menu > li > dt > .text { + font-weight: 600; + font-family: 'Open Sans'; + font-size: 14px; +} + +.bootstrap-select.btn-group .dropdown-menu .text-muted { + color: #999 !important; +} + +.bootstrap-select .caret { + border: 0; + width: auto; + height: auto; + margin-top: -10px !important; +} + +.bootstrap-select .caret:before { + content: "\f107"; + display: inline-block; + border: 0; + font-family: FontAwesome; + font-style: normal; + font-weight: normal; +} + +.bootstrap-select .selected i { + color: #aaa; +} + +/*** +Pace - Page Progress +***/ + +.pace .pace-progress { + z-index: 10000; + top: 40px; + height: 2px; +} + +.pace .pace-progress-inner { + box-shadow: none; +} + +.pace .pace-activity { + top: 44px; + right: 22px; + border-radius: 10px !important; +} + + +@media (max-width: 480px) { + + .page-header-fixed .pace .pace-progress { + top: 82px; + } + + .page-header-fixed .pace .pace-activity { + top: 88px; + right: 15px; + } + +} diff --git a/cas-server-webapp/src/main/webapp/assets/css/print.css b/cas-server-webapp/src/main/webapp/assets/css/print.css new file mode 100644 index 0000000..38f2dcc --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/print.css @@ -0,0 +1,38 @@ +body { + background-color: #fff !important; +} + +.header { + display: none; +} + +.page-sidebar { + display: none; +} + +.theme-panel { + display: none; +} + +.hidden-print { + display: none; +} + +.footer { + display: none; +} + +.no-page-break { + page-break-after: avoid; +} + +.page-container { + margin: 0px !important; + padding: 0px !important; +} + +.page-content { + min-height: auto !important; + padding: 0px 20px 20px !important; + margin: 0 !important; +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/css/style-metronic.css b/cas-server-webapp/src/main/webapp/assets/css/style-metronic.css new file mode 100644 index 0000000..80e6b29 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/style-metronic.css @@ -0,0 +1,1510 @@ +/* remove rounds from all elements */ + +div, +input, +select, +textarea, +span, +img, +table, +td, +th, +p, +a, +button, +ul, +code, +pre, +li { + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + border-radius: 0 !important; +} + +/*** +Buttons & Dropdown Buttons +***/ + +.btn { + border-width: 0; + padding: 7px 14px; + font-size: 14px; + outline: none !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + border-radius: 0 !important; + text-shadow: none; +} + +/* fix jumping group buttons */ +.btn-group.btn-group-solid .btn + .btn, +.btn-group.btn-group-solid .btn + .btn-group.btn-group-solid, +.btn-group.btn-group-solid .btn-group.btn-group-solid + .btn, +.btn-group.btn-group-solid .btn-group.btn-group-solid + .btn-group.btn-group-solid { + margin-left: 0px; +} + +.btn-group-vertical.btn-group-solid > .btn + .btn, +.btn-group-vertical.btn-group-solid > .btn + .btn-group, +.btn-group-vertical.btn-group-solid > .btn-group + .btn, +.btn-group-vertical.btn-group-solid > .btn-group + .btn-group { + margin-top: 0px; + margin-left: 0; +} + +.btn-default { + border-width: 1px; + padding: 6px 13px; +} + +.btn.red-stripe { + border-left: 3px solid #d84a38; +} + +.btn.blue-stripe { + border-left: 3px solid #4d90fe; +} + +.btn.purple-stripe { + border-left: 3px solid #852b99; +} + +.btn.green-stripe { + border-left: 3px solid #35aa47; +} + +.btn.yellow-stripe { + border-left: 3px solid #ffb848; +} + +.btn.dark-stripe { + border-left: 3px solid #555555; +} + +.btn.default { + color: #333333; + text-shadow: none; + background-color: #e5e5e5; +} +.btn.default:hover, +.btn.default:focus, +.btn.default:active, +.btn.default.active, +.btn.default[disabled], +.btn.default.disabled { + color: #333333; + background-color: #d8d8d8 !important; + outline: none !important; +} + +/* Red */ +.btn.red { + color: white; + text-shadow: none; + background-color: #d84a38; +} +.btn.red:hover, +.btn.red:focus, +.btn.red:active, +.btn.red.active, +.btn.red[disabled], +.btn.red.disabled { + background-color: #bb2413 !important; + color: #fff !important; + outline: none !important; +} + +/* Blue */ + +.btn.blue { + color: white; + text-shadow: none; + background-color: #4d90fe; +} +.btn.blue:hover, +.btn.blue:focus, +.btn.blue:active, +.btn.blue.active, +.btn.blue[disabled], +.btn.blue.disabled { + background-color: #0362fd !important; + color: #fff !important; + outline: none !important; +} + +.btn-group .btn.blue.dropdown-toggle { + background-color: #4d90fe !important; +} +.btn-group .btn.blue:hover, +.btn-group .btn.blue:focus, +.btn-group .btn.blue:active, +.btn-group .btn.blue.active, +.btn-group .btn.blue.disabled, +.btn-group .btn.blue[disabled] { + background-color: #0362fd !important; + color: #fff !important; + outline: none !important; +} + +/* Green */ +.btn.green { + color: white; + text-shadow: none; + background-color: #35aa47; +} +.btn.green:hover, +.btn.green:focus, +.btn.green:active, +.btn.green.active, +.btn.green.disabled, +.btn.green[disabled]{ + background-color: #1d943b !important; + color: #fff !important; + outline: none !important; +} + +/* Purple */ +.btn.purple { + color: white; + text-shadow: none; + background-color: #852b99; +} +.btn.purple:hover, +.btn.purple:focus, +.btn.purple:active, +.btn.purple.active, +.btn.purple.disabled, +.btn.purple[disabled] { + background-color: #6d1b81 !important; + color: #fff !important; + outline: none !important; +} + +.btn-group .btn.purple.dropdown-toggle { + background-color: #852b99 !important; +} +.btn-group .btn.purple:hover, +.btn-group .btn.purple:focus, +.btn-group .btn.purple:active, +.btn-group .btn.purple.active, +.btn-group .btn.purple.disabled, +.btn-group .btn.purple[disabled] { + background-color: #6d1b81 !important; + color: #fff !important; + outline: none !important; +} + +/* Yellow */ +.btn.yellow { + color: white; + text-shadow: none; + background-color: #ffb848; +} +.btn.yellow:hover, +.btn.yellow:focus, +.btn.yellow:active, +.btn.yellow.active, +.btn.yellow.disabled, +.btn.yellow[disabled] { + background-color: #eca22e !important; + color: #fff !important; + outline: none !important; +} + +.btn-group .btn.yellow.dropdown-toggle { + background-color: #ffb848 !important; +} +.btn-group .btn.yellow:hover, +.btn-group .btn.yellow:focus, +.btn-group .btn.yellow:active, +.btn-group .btn.yellow.active, +.btn-group .btn.yellow.disabled, +.btn-group .btn.yellow[disabled] { + background-color: #eca22e !important; + color: #fff !important; + outline: none !important; +} + +/* Black */ +.btn.dark { + color: white; + text-shadow: none; + background-color: #555555; +} +.btn.dark:hover, +.btn.dark:focus, +.btn.dark:active, +.btn.dark.active, +.btn.dark.disabled, +.btn.dark[disabled] { + background-color: #222222 !important; + color: #fff !important; + outline: none !important; +} + +.btn-group .btn.dark.dropdown-toggle { + background-color: #555555 !important; +} +.btn-group .btn.dark:hover, +.btn-group .btn.dark:focus, +.btn-group .btn.dark:active, +.btn-group .btn.dark.active, +.btn-group .btn.dark.disabled, +.btn-group .btn.dark[disabled] { + background-color: #222222 !important; + color: #fff !important; + outline: none !important; +} + +.btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + vertical-align: middle; +} + +.btn-lg > i { + font-size: 18px; +} + +.btn > i { + font-size: 14px; +} + +.btn-sm, +.btn-xs { + padding: 4px 10px 5px 10px; + font-size: 13px; + line-height: 1.5; +} + +.btn-sm > i, +.btn-xs > i { + font-size: 13px; +} + +.btn-xs { + padding: 1px 5px; +} + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; +} + +/*** +Metro icons +***/ + +[class^="m-icon-"] { + display: inline-block; + width: 14px; + height: 14px; + margin-top: 3px; + line-height: 14px; + vertical-align: top; + background-image: url(../img/syncfusion-icons.png); + background-position: 0 0; + background-repeat: no-repeat; +} + +[class^="m-icon-big-"] { + display: inline-block; + width: 30px; + height: 30px; + margin: 6px; + vertical-align: middle; + background-image: url(../img/syncfusion-icons.png); + background-position: 0 0px; + background-repeat: no-repeat; +} + +/* large icons */ +.btn.m-icon-big { + padding: 9px 16px 8px 16px; +} + +.btn.m-icon-big.m-icon-only{ + padding: 9px 8px 8px 0px; +} + +.btn.m-icon-big [class^="m-icon-big-"] { + margin: 0 0 0 10px; +} + +.btn.m-icon-ony > i { + margin-left: 0px; +} + +/* default icons */ +.btn.m-icon { + padding: 7px 14px 7px 14px; +} + +.btn.m-icon [class^="m-icon-"] { + margin: 4px 0 0 5px; +} + +.btn.m-icon.m-icon-only { + padding: 7px 10px 7px 6px; +} + +/* white icon */ +.m-icon-white { + background-image: url(../img/syncfusion-icons-white.png); +} + +/* Misc */ +.m-icon-swapright { + background-position: -27px -10px; +} +.m-icon-swapdown { + background-position: -68px -10px; +} +.m-icon-swapleft { + background-position: -8px -10px; +} +.m-icon-swapup { + background-position: -46px -10px; +} +.m-icon-big-swapright{ + background-position: -42px -28px; +} +.m-icon-big-swapdown{ + background-position: -115px -28px; +} +.m-icon-big-swapleft{ + background-position: -6px -28px; +} +.m-icon-big-swapup{ + background-position: -78px -28px; +} + + +/*** +Popover + ***/ +.popover { + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2); + padding: 0 !important; +} + +.popover .popover-title { + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + border-radius: 0 !important; + margin: 0 !important; +} + +.info .popover .popover-title, +.popover.info .popover-title, +.info .popover .popover-content, +.popover.info .popover-content { + color:#27a9e3; +} + +.success .popover .popover-title, +.popover.success .popover-title, +.success .popover .popover-content, +.popover.success .popover-content { + color:#468847; +} + +.error .popover .popover-title, +.popover.error .popover-title, +.error .popover .popover-content, +.popover.error .popover-content { + color:#B94A48; +} + +.warning .popover .popover-title, +.popover.warning .popover-title, +.warning .popover .popover-content, +.popover.warning .popover-content { + color:#C09853; +} + +.popovers.yellow + .popover { + background: yellow; +} + +.popovers.yellow + .popover .popover-title { + background: yellow; +} + +.popovers.yellow + .popover .popover-content { + background: yellow; +} + +/*** +Dropdown +***/ + + /*Fixing dropdown issue on mobile devices in Bootstrap 3.2.2*/ +.dropdown-backdrop { + position: static; +} + +.dropdown:hover .caret, +.open.dropdown .caret { + opacity: 1; + filter: alpha(opacity=100); +} + + +.dropdown.open .dropdown-toggle { + color: #08c; + background: #ccc; + background: rgba(0, 0, 0, 0.3); +} + +/*** +Dropdown Menu +***/ + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + list-style: none; + text-shadow: none; + padding: 0px; + margin:0px; + background-color: #ffffff; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + font-size: 14px; + font-family: "Segoe UI",Helvetica, Arial, sans-serif; + border: 1px solid #ddd; +} + +/* custom dropdown conetnt */ +.dropdown-content { + padding:5px; +} + +.dropdown-content form { + margin:0; +} + +.dropdown.inline .dropdown-menu { + display: inline-block; + position: relative; +} + +.dropdown-menu.bottom-up { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} + +.dropdown-menu li > a { + padding: 6px 0 6px 13px; + color: #333; + text-decoration: none; + display: block; + clear: both; + font-weight: normal; + line-height: 18px; + white-space: nowrap; +} + +.dropdown-menu li > a:hover, +.dropdown-menu .active > a, +.dropdown-menu .active > a:hover { + text-decoration: none; + background-image: none; + background-color: #eee; + color: #333; + filter:none; +} + +/* dropdown sub menu support for Bootsrap 3 */ +.dropdown-submenu { + position: relative; +} + +.dropdown-submenu > .dropdown-menu { + top: 5px; + left: 100%; + margin-top: -6px; + margin-left: -1px; +} + +.dropdown-submenu:hover > .dropdown-menu { + display: block; +} + +.dropup .dropdown-submenu > .dropdown-menu { + top: auto; + bottom: 0; + margin-top: 0; + margin-bottom: -2px; +} + +.dropdown-submenu > a:after { + position: absolute; + display: inline-block; + font-size: 14px; + right: 7px; + top: 7px; + font-family: FontAwesome; + height: auto; + content: "\f105"; + font-weight: 300; +} + +.dropdown-submenu:hover > a:after { + border-left-color: #ffffff; +} + +.dropdown-submenu.pull-left { + float: none; +} + +.dropdown-submenu.pull-left > .dropdown-menu { + left: -100%; + margin-left: 10px; +} + +.nav.pull-right > li > .dropdown-menu, +.nav > li > .dropdown-menu.pull-right { + right: 0; + left: auto; +} + +.nav.pull-right > li > .dropdown-menu:before, +.nav > li > .dropdown-menu.pull-right:before { + right: 12px; + left: auto; +} + +.nav.pull-right > li > .dropdown-menu:after, +.nav > li > .dropdown-menu.pull-right:after { + right: 13px; + left: auto; +} + +.nav.pull-right > li > .dropdown-menu .dropdown-menu, +.nav > li > .dropdown-menu.pull-right .dropdown-menu { + right: 100%; + left: auto; + margin-right: -1px; + margin-left: 0; + -webkit-border-radius: 6px 0 6px 6px; + -moz-border-radius: 6px 0 6px 6px; + border-radius: 6px 0 6px 6px; +} + +@media (max-width: 767px) { + + .navbar-nav .open .dropdown-menu { + position: absolute; + float: left; + width: auto; + margin-top: 0; + background-color: #ffffff; + border: 1px solid #ddd; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + } + + .navbar-nav .open .dropdown-menu > li > a { + padding: 6px 0 6px 13px; + color: #333 !important; + } + + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-color: #eee !important; + } + +} + +/*** +Dropdown Checkboxes (in v1.3) +***/ +.dropdown-checkboxes { + padding: 5px; +} + +.dropdown-checkboxes label { + display: block; + font-weight: 300; + color: #333; + margin-bottom: 4px; + margin-top: 4px; +} + + +/*** +Dropdown Menu Badges +***/ + +.dropdown-menu > li > a > .badge { + position: absolute; + margin-top: 1px; + right: 3px; + display: inline; + font-size: 11px; + font-weight: 300; + text-shadow:none; + height: 18px; + padding: 3px 6px 3px 6px; + text-align: center; + vertical-align: middle; + -webkit-border-radius: 12px !important; + -moz-border-radius: 12px !important; + border-radius: 12px !important; +} + +.dropdown-menu > li > a > .badge.badge-roundless { + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + border-radius: 0 !important; +} + +/* end: sidebar menu badges */ + +/*** +Forms +***/ +code { + border: 1px solid #e1e1e1; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); +} +label { + font-weight: 400; + font-size: 14px; +} + +.form-control:-moz-placeholder { + color: #999999; +} +.form-control::-moz-placeholder { + color: #999999; +} +.form-control:-ms-input-placeholder { + color: #999999; +} +.form-control::-webkit-input-placeholder { + color: #999999; +} +.form-control { + font-size: 14px; + font-weight: normal; + color: #333333; + background-color: #ffffff; + border: 1px solid #e5e5e5; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #999999; + outline: 0; + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eeeeee; +} + +.form-control.height-auto { + height: auto; +} + +.uneditable-input { + padding: 6px 12px; + min-width: 206px; + font-size: 14px; + font-weight: normal; + height: 34px; + color: #333333; + background-color: #ffffff; + border: 1px solid #e5e5e5; + border-radius: 0; + -webkit-box-shadow: none; + box-shadow: none; + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} + +label.form-control { + display: block; + margin-bottom: 5px; +} + +input[disabled], +select[disabled], +textarea[disabled] { + cursor: not-allowed; + background-color: #F4F4F4 !important; +} + +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: #F9F9F9 !important; +} + +/* input groups */ +.input-group.input-group-fixed { + width: auto !important; +} + +.input-group-addon { + border-color: #e5e5e5; + background: #e5e5e5; + min-width: 39px; +} + +.input-group-addon > i { + color: #999; +} + +/* form control sizing */ +.form-control-inline { + display: inline-block !important; +} + +.input-mini { + width: 45px !important; +} + +.input-xsmall { + width: 80px !important; +} + +.input-small { + width: 120px !important; +} + +.input-medium { + width: 240px !important; +} + +.input-large { + width: 320px !important; +} + +.input-xlarge { + width: 480px !important; +} + +.input-inline { + display: inline-block; + width: auto; + vertical-align: middle; +} + +.form-group .input-inline { + margin-right: 5px; +} + +.input-sm { + height: 28px; + padding: 5px 10px; + font-size: 13px; +} + +select.input-sm { + height: 28px; + line-height: 28px; + padding: 2px 10px; +} + +/*** +Input spinner(in v1.4) +***/ + +input[type="text"].spinner, +input[type="password"].spinner, +input[type="datetime"].spinner, +input[type="datetime-local"].spinner, +input[type="date"].spinner, +input[type="month"].spinner, +input[type="time"].spinner, +input[type="week"].spinner, +input[type="number"].spinner, +input[type="email"].spinner, +input[type="url"].spinner, +input[type="search"].spinner, +input[type="tel"].spinner, +input[type="color"].spinner { + background-image: url("../img/input-spinner.gif") !important; + background-repeat: no-repeat; + background-position: right 8px; +} + +@media (max-width: 768px) { + + .input-large { + width: 250px !important; + } + + .input-xlarge { + width: 300px !important; + } + +} + +/*** +Error States +***/ + +.has-warning .help-inline, +.has-warning .help-block, +.has-warning .control-label { + color: #c09853; +} + +.has-warning .form-control { + border-color: #c09853; + -webkit-box-shadow: none; + box-shadow: none; +} + +.has-warning .form-control:focus { + border-color: #a47e3c; + -webkit-box-shadow: none; + box-shadow: none; +} + +.has-warning .input-group-addon { + color: #c09853; + background-color: #fcf8e3; + border-color: #c09853; +} + +.has-error .help-inline, +.has-error .help-block, +.has-error .control-label { + color: #b94a48; +} + +.has-error .form-control { + border-color: #b94a48; + -webkit-box-shadow: none; + box-shadow: none; +} + +.has-error .form-control:focus { + border-color: #953b39; + -webkit-box-shadow: none; + box-shadow: none; +} + +.has-error .input-group-addon { + color: #b94a48; + background-color: #f2dede; + border-color: #b94a48; +} + +.has-success .help-inline, +.has-success .help-block, +.has-success .control-label { + color: #468847; +} + +.has-success .form-control { + border-color: #468847; + -webkit-box-shadow: none; + box-shadow: none; +} + +.has-success .form-control:focus { + border-color: #356635; + -webkit-box-shadow: none; + box-shadow: none; +} + +.has-success .input-group-addon { + color: #468847; + background-color: #dff0d8; + border-color: #468847; +} + +/*** +Custom label and badges +***/ + +.label, +.badge { + font-weight: 300; + text-shadow: none !important; +} + +.label { + font-size: 12px; + padding: 3px 6px 3px 6px; +} + +.label.label-sm { + font-size: 12px; + padding: 1px 4px 1px 4px; +} + +h1 .label, +h2 .label, +h3 .label, +h4 .label, +h5 .label, +h6 .label, +h7 .label { + font-size: 75%; +} + +.badge { + font-size: 11px !important; + font-weight: 300; + text-align: center; + background-color: #e02222; + height: 18px; + padding: 3px 6px 3px 6px; + -webkit-border-radius: 12px !important; + -moz-border-radius: 12px !important; + border-radius: 12px !important; + text-shadow:none !important; + text-align: center; + vertical-align: middle; +} + +.badge.badge-roundless { + -webkit-border-radius: 0 !important; + -moz-border-radius: 0 !important; + border-radius: 0 !important; +} + +.badge-default, +.label-default { + background-color: #999 !important; +} + +.badge-primary, +.label-primary { + background-color: #428bca !important; +} + +.label-success, +.badge-success { + background-color: #3cc051; + background-image: none !important; +} + +.label-warning, +.badge-warning { + background-color: #fcb322; + background-image: none !important; +} + +.label-danger, +.badge-danger { + background-color: #ed4e2a; + background-image: none !important; +} + +.label-info, +.badge-info { + background-color: #57b5e3; + background-image: none !important; +} + +/* fix badge position for navs */ +.nav.nav-pills > li > a > .badge { + margin-top: -2px; +} + +.nav.nav-stacked > li > a > .badge { + margin-top: 1px; + margin-bottom: 0px; +} + +/*** +Iconic Labels +***/ + +.label.label-icon { + padding: 4px 1px 4px 5px; + margin-right: 2px; + text-align: center !important; +} + +.ie9 .label.label-icon, +.ie10 .label.label-icon { + padding: 3px 0px 3px 3px; +} + +.label.label-icon > i { + font-size: 12px; + text-align: center !important; +} + + +/*** +Progress Bars +***/ + +.progress { + border: 0; + background-image: none !important; + filter: none !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; + +} + +.progress > .progress-bar-success { + background-color: #3cc051; +} + +.progress > .progress-bar-danger { + background-color: #ed4e2a; +} + +.progress > .progress-bar-info { + background-color: #57b5e3; +} + +.progress > .progress-bar-warning { + background-color: #fcb322; +} + + +/*** +Pagination +***/ +.pagination { + margin: 10px 0; +} + +.pagination .active > a, +.pagination .active > a:hover { + background: #eee; + border-color: #dddddd; + color: #333; +} + + + +/*** +wells +***/ +.well { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +/* Bootstrap Tabs */ + +.dropup.open > .dropdown-toggle, +.dropdown.open > .dropdown-toggle { + border-color: #ddd !important; +} + +.nav-tabs > li > .dropdown-menu:after, +.nav-pills > li > .dropdown-menu:after, +.navbar-nav > li > .dropdown-menu:after, + +.nav-tabs > li > .dropdown-menu:before, +.nav-pills > li > .dropdown-menu:before, +.navbar-nav > li > .dropdown-menu:before { + display: none !important; +} + +.nav-tabs > .dropdown.open > .dropdown-toggle, +.nav-pills > .dropdown.open > .dropdown-toggle { + background: #eee !important; + color: #0d638f !important; +} + + +.nav-tabs, +.nav-pills { + margin-bottom: 10px; +} + +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} + +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} + +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + -webkit-border-radius: 4px 0 0 4px; + -moz-border-radius: 4px 0 0 4px; + border-radius: 4px 0 0 4px; +} + +.tabs-left > .nav-tabs > li > a:hover, +.tabs-left > .nav-tabs > li > a:focus { + border-color: #eeeeee #dddddd #eeeeee #eeeeee; +} + +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover, +.tabs-left > .nav-tabs .active > a:focus { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: #ffffff; +} + +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} + +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + -webkit-border-radius: 0 4px 4px 0; + -moz-border-radius: 0 4px 4px 0; + border-radius: 0 4px 4px 0; +} + +.tabs-right > .nav-tabs > li > a:hover, +.tabs-right > .nav-tabs > li > a:focus { + border-color: #eeeeee #eeeeee #eeeeee #dddddd; +} + +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover, +.tabs-right > .nav-tabs .active > a:focus { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: #ffffff; +} + +.tabs-below > .nav-tabs, +.tabs-below > .nav-pills { + border-bottom: 0; + margin-bottom: 0px; + margin-top: 10px; +} + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; + margin-bottom: 0; + margin-top: 10px; +} + +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} + +.tabs-below > .nav-tabs > li > a { + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; +} + +.tabs-below > .nav-tabs > li > a:hover, +.tabs-below > .nav-tabs > li > a:focus { + border-top-color: #ddd; + border-bottom-color: transparent; +} + +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover, +.tabs-below > .nav-tabs > .active > a:focus { + border-color: transparent #ddd #ddd #ddd; +} + +/* BS3.0.3 removed tabbable class so its added back */ +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +.tabbable:before, +.tabbable:after { + display: table; + content: " "; +} + +.tabbable:after { + clear: both; +} + +/*** +Bootstrap modal +***/ + +.modal { + z-index: 10050 !important; + outline: none !important; +} + +.modal-header { + border-bottom: 1px solid #EFEFEF; +} + +.modal-header h3{ + font-weight: 300; +} + +.modal-small.modal-dialog { + width: 400px; +} + +.modal-wide.modal-dialog { + width: 60%; +} + +.modal-full.modal-dialog { + width: 100%; +} + +@media (max-width: 768px) { + + .modal-small.modal-dialog, + .modal-wide.modal-dialog, + .modal-full.modal-dialog { + width: auto; + } + +} + +/*** +Modal header close button fix +***/ +.modal-header .close { + margin-top: 0px !important; +} + +.modal > .loading { + position: absolute; + top: 50%; + left:50%; + margin-top: -22px; + margin-left: -22px; +} + +.modal-backdrop { + border: 0 !important; + outline: none !important; + z-index: 10049 !important; +} + +.modal-backdrop, +.modal-backdrop.fade.in { + background-color: #333 !important; +} + +/* fix: content shifting to the right on modal open due to scrollbar closed */ +.modal { + overflow-y: auto !important; +} + +.modal-open { + overflow-y: auto !important; +} + +.modal-open-noscroll { + overflow-y: hidden !important; +} + + +/*** +Image Carousel +***/ +.carousel.image-carousel .carousel-inner { + padding-top: 0; + padding-bottom: 0; +} + +.carousel.image-carousel .carousel-control i { + position: absolute; + top:40%; +} + +.carousel.image-carousel.image-carousel-hoverable .carousel-control i { + display: none; +} + +.carousel.image-carousel.image-carousel-hoverable:hover .carousel-control i { + display: inline-block; +} + +.carousel.image-carousel .carousel-control.left i { + left:10px; +} + +.carousel.image-carousel .carousel-control.right i { + right:10px; +} + +.carousel.image-carousel .carousel-indicators { + margin-top: 10px; + bottom: -7px; +} + +.carousel.image-carousel .carousel-indicators li { + background-color: #666; +} + +.carousel.image-carousel .carousel-indicators li.active { + background-color: #333; +} + +.carousel.image-carousel .carousel-caption { + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 15px 15px 25px 15px; + background: #333333; + background: rgba(0, 0, 0, 0.75); +} + +.carousel.image-carousel .carousel-caption h4, +.carousel.image-carousel .carousel-caption h3, +.carousel.image-carousel .carousel-caption h2, +.carousel.image-carousel .carousel-caption p { + text-align: left; + line-height: 20px; + color: #ffffff; +} + +.carousel.image-carousel .carousel-caption h2, +.carousel.image-carousel .carousel-caption h3, +.carousel.image-carousel .carousel-caption h4 { + margin: 0 0 5px; +} + +.carousel.image-carousel .carousel-caption h2 a, +.carousel.image-carousel .carousel-caption h3 a, +.carousel.image-carousel .carousel-caption h4 a { + color: #aaa; +} + +.carousel.image-carousel .carousel-caption p { + margin-bottom: 0; +} + +.carousel.image-carousel .item { + margin: 0; +} + +/*** +Bootstrap Tables +***/ + +.table thead > tr > th { + border-bottom: 0; +} + +.table tbody tr.active td, +.table tbody tr.active th { + background-color: #e9e9e9 !important; +} + +.table tbody tr.active:hover td, +.table tbody tr.active:hover th { + background-color: #e1e1e1 !important; +} + +.table-striped tbody tr.active:nth-child(odd) td, +.table-striped tbody tr.active:nth-child(odd) th { + background-color: #017ebc; +} + +.table .heading > th { + background-color: #eee !important; +} + +/*** +Bootstrap Panel +***/ + +.panel { + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.panel .panel-title > a:hover { + text-decoration: none; +} + +.accordion .panel-heading { + padding: 0; +} + +.accordion .panel-title { + padding: 0; +} + +.accordion .panel-title .accordion-toggle { + display: block; + padding: 10px 15px; +} + +.accordion .accordion-toggle.accordion-toggle-styled { + background: url("../img/accordion-plusminus.png") no-repeat; + background-position: right -19px; + margin-right: 15px; +} + +.accordion .accordion-toggle.accordion-toggle-styled.collapsed { + background-position: right 12px; +} + +/*** +Responsive Image +***/ +.table td .img-responsive{ + width:100%; +} + +/*** +Unstyled List +***/ + +.list-unstyled li > .list-unstyled { + margin-left: 25px; +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/css/style-responsive.css b/cas-server-webapp/src/main/webapp/assets/css/style-responsive.css new file mode 100644 index 0000000..018ef60 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/style-responsive.css @@ -0,0 +1,980 @@ +/*** +Responsive Theme. +Based on http://getbootstrap.com/css/#responsive-utilities-classes +***/ + +/*** +Fixed Footer +***/ + +.page-footer-fixed.page-footer-fixed-mobile .footer { + position: fixed; + left: 0; + right: 0; + z-index: 10000; + bottom: 0; +} + +.page-footer-fixed.page-footer-fixed-mobile .page-container { + margin-bottom: 20px !important; +} + +.page-footer-fixed.page-footer-fixed-mobile.page-sidebar-fixed .footer { + margin-left: 0 !important; +} + +/*** +Form Medium Devices Up To Large Devices +***/ + +@media (min-width: 992px) and (max-width: 1200px) { + + .page-boxed .header.navbar .dropdown .username { + display: none; + } + +} + +@media (max-width: 1024px) { + .hidden-1024 { + display: none; + } +} + +/*** +From Medium Devices Up To Larger Devices +***/ + +@media (min-width: 992px) { + + /*** + Page sidebar + ***/ + .page-sidebar { + width: 225px; + float: left; + position: relative; + margin-right: -100%; + } + + .page-sidebar.navbar-collapse { + max-height: none !important; + } + + /*** + Page content + ***/ + .page-content-wrapper { + float: left; + width: 100%; + } + + .page-content { + margin-left: 225px; + margin-top: 0px; + min-height: 600px; + padding: 25px 20px 20px 20px; + } + + .page-content.no-min-height { + min-height: auto; + } + + /*** + Footer + ***/ + .footer { + clear: left; + } + + /*** + Fixed Sidebar + ***/ + .page-sidebar-fixed .page-content { + min-height: 600px; + } + + .page-sidebar-fixed .page-sidebar { + position: fixed !important; + margin-left: 0; + top: 41px; + } + + .page-sidebar-fixed ul.page-sidebar-menu > li.last { + margin-bottom: 15px !important; + } + + .page-sidebar-fixed.page-sidebar-hover-on .page-sidebar { + z-index: 10000; + width: 35px; + } + + .page-sidebar-fixed.page-sidebar-hover-on .page-sidebar .selected { + display: none; + } + + .page-sidebar-fixed.page-sidebar-hover-on .page-content { + margin-left: 35px; + } + + .page-sidebar-fixed.page-sidebar-hover-on .footer { + margin-left: 35px; + } + + .page-sidebar-fixed .page-sidebar-closed .page-sidebar .sidebar-search .submit, + .page-sidebar-fixed .page-sidebar .sidebar-toggler { + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; + -o-transition: all 0.3s ease; + transition: all 0.3s ease; + } + + .page-sidebar-fixed.page-sidebar-reversed .page-sidebar-closed .page-sidebar .sidebar-search .submit, + .page-sidebar-fixed.page-sidebar-reversed .page-sidebar .sidebar-toggler { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + } + + .page-sidebar-hovering { + overflow: hidden !important; + } + + .page-sidebar-hovering .sub-menu, + .page-sidebar-hovering span.badge, + .page-sidebar-hovering span.title, + .page-sidebar-hovering span.arrow { + display: none !important; + } + + .page-sidebar-hovering .submit { + opacity: 0; + width: 0 !important; + height: 0 !important; + } + + /*** + Fixed Sidebar + ***/ + + .page-sidebar-fixed .footer { + margin-left: 225px; + background-color: #fff; + padding: 8px 20px 5px 20px; + } + + .page-sidebar-fixed .footer .footer-inner { + color: #333; + } + + .page-sidebar-fixed.page-sidebar-closed .footer { + margin-left: 35px; + } + + .page-sidebar-fixed .footer .footer-tools .go-top { + background-color: #666; + } + + .page-sidebar-fixed .footer .footer-tools .go-top i { + color: #ddd; + } + + /*** + Boxed Layout + ***/ + + .page-boxed .header.navbar .navbar-brand { + margin-left: 0px !important; + width: 226px; + } + + .page-boxed .header.navbar .navbar-brand img { + margin-left: 10px; + } + + .page-boxed .header.navbar .navbar-nav { + margin-right: 0px; + } + + .page-boxed .footer { + padding: 8px 0 5px 0; + } + + .page-boxed.page-sidebar-fixed .footer { + padding-right: 20px; + padding-left: 20px; + } + + /*** + Sidebar Reversed + ***/ + + .page-sidebar-reversed .page-sidebar { + float: right; + margin-right: 0; + margin-left: -100%; + } + + .page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + margin-left: -225px; + } + + .page-sidebar-reversed .page-content { + margin-left: 0; + margin-right: 225px; + } + + .page-sidebar-reversed.page-sidebar-fixed .page-sidebar-wrapper { + position: relative; + float: right; + } + + .page-sidebar-reversed.page-sidebar-fixed .footer { + margin-left: 0; + margin-right: 225px; + padding: 8px 20px 5px 20px; + } + + .page-sidebar-reversed.page-sidebar-fixed.page-footer-fixed .footer { + margin-left: 0; + margin-right: 0; + } + + .page-sidebar-reversed.page-sidebar-fixed.page-sidebar-hover-on .page-content { + margin-left: 0; + margin-right: 35px; + } + + .page-sidebar-reversed.page-sidebar-fixed.page-sidebar-hover-on .footer { + margin-right: 35px; + } + + /*** + Sidebar Closed + ***/ + + .page-sidebar-closed .page-sidebar .sidebar-toggler { + margin-left: 3px; + } + + .page-sidebar-closed .page-sidebar .sidebar-search .form-container { + width: 29px; + margin-left: 3px; + } + + .page-sidebar-closed .page-sidebar .sidebar-search .form-container .input-box { + border-bottom: 0 !important; + } + + .page-sidebar-closed .page-sidebar .sidebar-search .form-container input[type="text"] { + display: none; + } + + .page-sidebar-closed .page-sidebar .sidebar-search .form-container .submit { + margin-top: 5px !important; + margin-left: 7px !important; + margin-right: 7px !important; + display: block !important; + } + + .page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + width: 255px; + position: relative; + z-index: 1; + padding-top: 0px; + } + + .page-sidebar-closed .page-sidebar .sidebar-search.open .form-container input[type="text"] { + margin-top: 7px; + margin-left: 8px; + padding-left: 10px; + padding-bottom: 2px; + width: 185px; + display: inline-block !important; + } + + .page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .submit { + display: inline-block; + width: 13px; + height: 13px; + margin: 11px 8px 9px 6px !important; + } + + .page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-repeat: no-repeat; + width: 11px; + height: 11px; + margin: 12px 8px 9px 8px !important; + display: inline-block !important; + float: left !important; + } + + .page-sidebar-closed .page-sidebar-menu > li > a .selected { + right: -3px !important; + } + + .page-sidebar-closed .page-sidebar-menu > li > a > .badge, + .page-sidebar-closed .page-sidebar-menu > li > a > .title, + .page-sidebar-closed .page-sidebar-menu > li > a > .arrow { + display: none !important; + } + + .page-sidebar-closed .page-sidebar .sidebar-toggler { + margin-right: 3px; + } + + .page-sidebar-closed .page-sidebar .sidebar-search { + margin-top: 6px; + margin-bottom: 6px; + } + + .page-sidebar-closed .page-sidebar-menu { + width: 35px !important; + } + + .page-sidebar-closed .page-sidebar-menu > li > a { + padding-left: 7px; + } + + .page-sidebar-fixed.page-sidebar-closed .page-sidebar-menu > li > a { + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; + } + + .page-sidebar-reversed.page-sidebar-fixed.page-sidebar-closed .page-sidebar-menu > li > a { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover { + width: 236px !important; + position: relative !important; + z-index: 2000; + display: block !important; + } + + .page-sidebar-closed .page-sidebar-menu > li.sidebar-toggler-wrapper:hover, + .page-sidebar-closed .page-sidebar-menu > li.sidebar-search-wrapper:hover { + width: 35px !important; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover .selected { + display: none; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover > a > i { + margin-right: 10px; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover .title { + display: inline !important; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover .badge { + display: block !important; + } + + .page-sidebar-closed .page-sidebar-menu > li > .sub-menu { + display: none !important; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover > .sub-menu { + width: 200px; + position: absolute; + z-index: 2000; + left: 36px; + margin-top: 0; + top: 100%; + display: block !important; + } + + .page-sidebar-closed .page-sidebar-menu > li:hover > .sub-menu > li > .sub-menu, + .page-sidebar-closed .page-sidebar-menu > li:hover > .sub-menu > li > .sub-menu > li > .sub-menu { + width: 200px; + } + + /* 2rd level sub menu*/ + .page-sidebar-closed .page-sidebar-menu > li:hover > .sub-menu > li > a { + padding-left: 15px !important; + } + + /* 3rd level sub menu*/ + .page-sidebar-closed .page-sidebar-menu > li > ul.sub-menu > li > .sub-menu > li > a { + padding-left: 30px !important; + } + + /* 4rd level sub menu*/ + .page-sidebar-closed .page-sidebar-menu > li > ul.sub-menu > li > .sub-menu > li > .sub-menu > li > a { + padding-left: 45px !important; + } + + /* sidebar container */ + + .page-sidebar-closed .page-sidebar { + width: 35px; + } + + .page-sidebar-closed .page-content { + margin-left: 35px !important; + } + + /*** + Sidebar Reversed & Sidebar Closed + ***/ + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar { + margin-left: -35px; + width: 35px; + } + + .page-sidebar-reversed.page-sidebar-closed .page-content { + margin-left: 0 !important; + margin-right: 35px !important; + } + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar-menu > li:hover { + margin-left: -201px; + } + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar-menu > li.sidebar-toggler-wrapper:hover, + .page-sidebar-reversed.page-sidebar-closed .page-sidebar-menu > li.sidebar-search-wrapper:hover { + margin-left: 0; + } + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + margin-left: -225px; + } + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .submit { + margin: 11px 8px 9px 12px !important; + float: left !important; + } + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + margin: 12px 6px 9px 8px !important; + float: right !important; + } + + .page-sidebar-reversed.page-sidebar-closed .page-sidebar-menu > li:hover > .sub-menu { + left:auto; + right: 36px; + } + + .page-sidebar-reversed.page-sidebar-fixed.page-sidebar-closed .footer { + margin-right: 35px; + } + + /*** + Fixed Footer + ***/ + + .page-footer-fixed .footer { + position: fixed; + left: 0; + right: 0; + z-index: 10000; + bottom: 0; + } + + .page-footer-fixed .page-container { + margin-bottom: 20px !important; + } + + .page-footer-fixed.page-sidebar-fixed .footer { + margin-left: 0 !important; + } + +} + +/*** +Up To Medium Devices +***/ + +@media (max-width:991px) { + + /*** + Page header + ***/ + .header.navbar { + padding: 0 20px 0 20px; + position: relative; + clear: both; + } + + .page-header-fixed.page-header-fixed-mobile .navbar-fixed-top { + position: fixed; + } + + .header.navbar .navbar-toggle { + display: inline-block; + } + + .page-sidebar.navbar-collapse { + max-height: none; /* set some max height to have a scrollable menu on mobile devices */ + } + + .page-sidebar.navbar-collapse.collapse { + display: none !important; + } + + .page-sidebar.navbar-collapse.in { + overflow: hidden !important; + overflow-y: auto !important; + display: block !important; + } + + .page-full-width .page-sidebar-menu { + display: block; + } + + .page-sidebar.navbar-collapse.navbar-no-scroll { + max-height: none !important; + } + + .header.navbar .nav li.dropdown i { + display: inline-block; + position: relative; + top:1px; + right:0px; + } + + .header.navbar .navbar-nav { + display: block; + margin-bottom: 0px !important; + } + + .header.navbar .navbar-nav .open .dropdown-menu { + position: absolute; + } + + .header.navbar .navbar-nav { + display: inline-block; + margin: 0 10px 0 0; + } + + .header.navbar .navbar-nav > li { + float: left; + } + + .header.navbar .navbar-brand { + margin-left: 0px !important; + padding-left: 0px !important; + } + + .header.navbar .navbar-brand img { + margin-left: 4px !important; + } + + + /*** + Header Search Box + ***/ + + .header.navbar .search-form { + display: none; + } + + .page-sidebar .header.navbar-responsive-search { + display: block; + } + + /*** + Page container + ***/ + .page-container { + margin: 0 !important; + padding: 0 !important; + } + + .page-header-fixed.page-header-fixed-mobile .page-container { + margin-top: 42px !important; + } + + /*** + Page content + ***/ + .page-content { + margin: 0px !important; + padding: 20px 20px 20px 20px !important; + min-height: 280px; + } + + /*** + Page sidebar + ***/ + .page-sidebar { + border-top: 0 !important; + margin: 20px; + } + + .page-sidebar.in { + border-top: 0 !important; + margin: 20px; + position: relative; + z-index: 5; + } + + .header.navbar .sidebar-toggler, + .page-sidebar .sidebar-toggler { + display: none; + } + + .page-sidebar ul { + margin-top:0px; + width:100%; + } + + .page-sidebar .selected { + display: none !important; + } + + .page-sidebar .sidebar-search .input-box { + width: 220px; + } + + /*** + Styler panel + ***/ + .styler-panel { + top:55px; + right:20px; + } + + /*** + Boxed Layout + ***/ + .page-boxed .header.navbar > .container, + .page-boxed .footer > .container, + .page-boxed > .container { + max-width: none !important; + margin: 0 !important; + padding: 0 !important; + } + +} + +/*** +From Small Devices Up To Medium Devices +***/ + +@media (min-width: 768px) and (max-width: 991px) { + + /*** + Body + ***/ + body { + padding-top: 0px; + } + + /*** + Page sidebar + ***/ + .page-sidebar .btn-navbar.collapsed .arrow { + display: none; + } + + .page-sidebar .btn-navbar .arrow { + position: absolute; + right: 25px; + width: 0; + height: 0; + top:50px; + border-bottom: 15px solid #5f646b; + border-left: 15px solid transparent; + border-right: 15px solid transparent; + } + + /*** + Boxed Layout + ***/ + .page-boxed .header.navbar > .container, + .page-boxed > .container { + margin: auto !important; + } + + .page-boxed .header.navbar { + margin: auto !important; + padding: 0; + } + + .page-boxed .footer { + padding-left: 0; + padding-right: 0; + } + +} + +/*** +Extra Small Devices Only +***/ + +@media (max-width: 767px) { + + /*** + Page header + ***/ + + .header.navbar { + padding: 0 10px 0 10px; + } + + .header.navbar .top-nav .nav{ + margin-top: 0px; + margin-right: 5px; + } + + .header.navbar .nav > li > .dropdown-menu.notification:after, + .header.navbar .nav > li > .dropdown-menu.notification:before { + margin-right: 160px; + } + + .header.navbar .nav > li > .dropdown-menu.notification { + margin-right: -160px; + } + + .header.navbar .nav > li > .dropdown-menu.inbox:after, + .header.navbar .nav > li > .dropdown-menu.inbox:before { + margin-right: 110px; + } + + .header.navbar .nav > li > .dropdown-menu.inbox { + margin-right: -110px; + } + + .header.navbar .nav > li > .dropdown-menu.tasks:after, + .header.navbar .nav > li > .dropdown-menu.tasks:before { + margin-right: 60px; + } + + .header.navbar .nav > li > .dropdown-menu.tasks { + margin-right: -60px; + } + + /* Header logo */ + .header.navbar .navbar-brand { + margin-left: 0px !important; + width: 110px; + } + + /*** + Page content + ***/ + .page-content { + padding: 20px 10px 10px 10px !important; + overflow: hidden; + } + + /*** + Page title + ***/ + .page-title { + margin-bottom: 20px; + font-size: 18px; + } + + .page-title small { + font-size: 13px; + padding-top: 3px; + } + + /*** + Styler pagel + ***/ + .styler-panel { + top:58px; + right:12px; + } + + /*** + Page breadcrumb + ***/ + .breadcrumb { + padding-left: 10px; + padding-right: 10px; + } + + /*** + Portlet form action + ***/ + .portlet-body.form .form-actions{ + padding-left: 15px; + } + + + /*** + Form input validation states + ***/ + .input-icon .input-error, + .input-icon .input-warning, + .input-icon .input-success { + top:-27px; + float: right; + right:10px !important; + } + + /*** + Advance tables + ***/ + .table-advance tr td.highlight:first-child a { + margin-left: 8px; + } + + /*** + Footer + ***/ + .footer { + padding-left: 10px; + padding-right: 10px; + } + + .footer .go-top { + float: right; + display: block; + margin-right: 0px; + } + + /*** + Vertical inline menu + ***/ + .ver-inline-menu li.active:after { + display: none; + } + + /*** + Form controls + ***/ + .form-horizontal .form-actions { + padding-left: 180px; + } + + .portlet .form-horizontal .form-actions { + padding-left: 190px; + } +} + +/*** +The Most Extra Small Devices Landscape Mode Only +***/ + +@media (max-width: 580px) { + + .header.navbar .username { + display: none; + } + +} + +@media (max-width: 480px) { + + /*** + Header navbar + ***/ + .page-header-fixed.page-header-fixed-mobile .header.navbar { + height: 84px; + } + + .page-header-fixed.page-header-fixed-mobile .page-container { + margin-top: 84px !important; + } + + .header.navbar .navbar-nav { + display: block; + clear: both; + margin-top: 2px; + margin-right: 0; + } + + .header.navbar .navbar-nav > li.dropdown .dropdown-toggle { + margin-top:-1px; + padding-left: 9px; + padding-right: 9px; + } + + .header.navbar .navbar-nav > li.dropdown.language .dropdown-toggle, + .header.navbar .navbar-nav > li.dropdown.user .dropdown-toggle { + padding-left: 4px; + padding-right: 0px; + } + + .header.navbar .navbar-nav li.dropdown .dropdown-toggle .badge { + top: 8px; + } + + /*** + Page sidebar + ***/ + .page-sidebar, + .page-sidebar.in { + margin: 0 10px 10px 10px; + } + + .page-header-fixed.page-header-fixed-mobile .page-sidebar, + .page-header-fixed.page-header-fixed-mobile .page-sidebar.in { + margin-top: 10px; + } + + /*** + Page title + ***/ + .page-title small { + display: block; + clear: both; + } + + /*** + Forms + ***/ + .portlet .form-horizontal .form-actions { + padding-left: 10px; + } + + /*** + Dashboard date range panel + ***/ + .page-content .breadcrumb .dashboard-date-range { + padding-bottom: 8px; + } + + .page-content .breadcrumb .dashboard-date-range span { + display: none; + } + + .page-content .breadcrumb > .btn-group span { + display: none; + } + + .page-content .breadcrumb > .btn-group > .btn { + padding-left: 7px; + padding-right: 7px; + } + + /*** + Hidden phone + ***/ + .hidden-480 { + display: none !important; + } +} + +/*** +The Most Extra Small Devices Portrait Mode Only +***/ + +@media (max-width: 320px) { + + /*** + Hidden phone + ***/ + .hidden-320 { + display: none; + } + + .header.navbar .navbar-brand { + width: 100px; + } +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/css/style.css b/cas-server-webapp/src/main/webapp/assets/css/style.css new file mode 100644 index 0000000..fdf8bb1 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/style.css @@ -0,0 +1,4355 @@ +/* +Template Name: Metronic - Responsive Admin Dashboard Template build with Twitter Bootstrap 3.1.1 +Version: 2.0.2 +Author: KeenThemes +Website: http://www.keenthemes.com/ +Contact: support@keenthemes.com +Purchase: http://themeforest.net/item/metronic-responsive-admin-dashboard-template/4021469?ref=keenthemes +License: You must have a valid license purchased only from themeforest(the above link) in order to legally use the theme for your project. +*/ + +/********************* + GENERAL UI COLORS +*********************/ + +/*** +Colors +blue: #4b8df8 +light blue: #bfd5fa +red: #e02222 +yellow: #ffb848 +green: #35aa47 +purple: #852b99 +dark: #555555; +light grey: #fafafa; +***/ + +/********************* + GENERAL RESET & SETUP +*********************/ + +/*** +Reset and overrides +***/ + +/* general body settings */ +body { + color: #000; + font-family: 'Open Sans', sans-serif; + padding: 0px !important; + margin: 0px !important; + font-size:13px; + direction: ltr; +} + +/* +Internet Explorer 10 doesn't differentiate device width from viewport width, and thus doesn't +properly apply the media queries in Bootstrap's CSS. To address this, +you can optionally include the following CSS and JavaScript to work around this problem until Microsoft issues a fix. +*/ +@-webkit-viewport { + width: device-width; +} + +@-moz-viewport { + width: device-width; +} + +@-ms-viewport { + width: device-width; +} + +@-o-viewport { + width: device-width; +} + +@viewport { + width: device-width; +} + +/* Internet Explorer 10 doesn't differentiate device width from viewport width, +and thus doesn't properly apply the media queries in Bootstrap's CSS. To address this, following CSS code applied */ +@-ms-viewport { + width: auto !important; +} + +/*** +Custom Scrollbars +***/ + +::-webkit-scrollbar { + width: 12px; +} + +::-webkit-scrollbar-track { + background-color: #eaeaea; + border-left: 1px solid #cecece; +} + +::-webkit-scrollbar-thumb { + background-color: #cecece; +} + +::-webkit-scrollbar-thumb:hover { + background-color: #aaa; +} + +::-webkit-scrollbar-track { + border-radius: 0; + box-shadow: none; + border: 0; +} + +::-webkit-scrollbar-thumb { + border-radius: 0; + box-shadow: none; + border: 0; +} + +/*** +General typography +***/ +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small { + color: #444; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Open Sans', sans-serif; + font-weight: 300 !important; +} + +h1.block, +h2.block, +h3.block, +h4.block, +h5.block, +h6.block { + padding-top: 10px; + padding-bottom: 10px; +} + +a { + text-shadow: none !important; + color: #0d638f; +} + +/*** +Fix link outlines after click +***/ +a,a:focus, a:hover, a:active { + outline: 0; +} + +/*** +General backgrounds. Can be applied to any block or panel +***/ + +.bg-blue { + background-image: none !important; + background-color: #4b8df8 !important; + border-color:#4b8df8 !important; + color: #fff !important; +} + +.bg-red { + background-image: none !important; + background-color: #e02222 !important; + border-color: #e02222 !important; + color: #fff !important; +} + +.bg-yellow { + background-image: none !important; + background-color: #ffb848 !important; + border-color: #ffb848 !important; + color: #fff !important; +} + +.bg-green { + background-image: none !important; + background-color: #35aa47 !important; + border-color: #35aa47 !important; + color: #fff !important; +} + +.bg-purple { + background-image: none !important; + background-color: #852b99 !important; + border-color: #852b99 !important; + color: #fff !important; +} + +.bg-dark { + background-image: none !important; + background-color: #555555 !important; + border-color: #555555 !important; + color: #fff !important; +} + +.bg-grey { + background-image: none !important; + background-color: #fafafa !important; + border-color: #fafafa !important; +} + +/*** +Font Awesome Icons +***/ + +[class^="fa-"], +[class*=" fa-"] { + display: inline-block; + margin-top: 1px; + font-size: 14px; + *margin-right: .3em; + line-height: 14px; +} + +/*** +Make font awesome icons fixed width(latest version issue) +***/ + +li [class^="fa-"], +li [class*=" fa-"] { + display: inline-block; + width: 1.25em; + text-align: center; +} +li [class^="fa-"].icon-large, +li [class*=" fa-"].icon-large { + /* increased font size for icon-large */ + width: 1.5625em; +} + +.fa-lg, +.icon-lg { + font-size: 16px; +} + +.fa-2x { + font-size: 2em; +} +.fa-3x { + font-size: 3em; +} +.fa-4x { + font-size: 4em; +} +.fa-5x { + font-size: 5em; +} + +.icon-default { + color: #ccc; +} + +.icon-success { + color: #468847; +} + +.icon-info { + color: #27a9e3; +} + +.icon-warning { + color: #dbc056; +} + +.icon-danger { + color: #B94A48; +} + +/*** +Close icon used for modal dialog and other UI element close buttons +***/ +.close { + display: inline-block; + margin-top: 0px; + margin-right: 0px; + width: 9px; + height: 9px; + background-repeat: no-repeat !important; + text-indent: -10000px; + outline: none; + background-image: url("../img/remove-icon-small.png") !important; +} + +/*** +General HR +***/ + +hr { + margin: 20px 0; + border: 0; + border-top: 1px solid #E0DFDF; + border-bottom: 1px solid #FEFEFE; +} + +/*** +Tools +***/ + +.display-none, +.display-hide { + display: none; +} + +.no-space { + margin: 0px !important; + padding: 0px !important; +} + +.no-margin { + margin:0; +} + +.no-border { + border:0 !important; +} + +.margin-bottom-5 { + margin-bottom: 5px; +} + +.margin-bottom-10 { + margin-bottom: 10px !important; +} + +.margin-top-10 { + margin-top: 10px !important; +} + +.margin-bottom-15 { + margin-bottom: 15px !important; +} + +.margin-bottom-20 { + margin-bottom: 20px !important; +} + +.margin-top-20 { + margin-top: 20px !important; +} + +.margin-bottom-25 { + margin-bottom: 25px !important; +} + +.margin-right-10 { + margin-right: 10px !important; +} + +.bold { + font-weight:600 !important; +} + +.fix-margin { + margin-left: 0px !important +} + +.border { + border: 1px solid red; +} + +.inline { + display: inline; +} + +.text-align-reverse { + text-align: right; +} + +/*** +ie8 & ie9 modes +***/ + +.visible-ie8 { + display: none; +} + +.ie8 .visible-ie8 { + display: inherit !important; +} + +.visible-ie9 { + display: none; +} + +.ie9 .visible-ie9 { + display: inherit !important; +} + +.hidden-ie8 { + display: inherit; +} + +.ie8 .hidden-ie8 { + display: none !important; +} + +.hidden-ie9 { + display: inherit; +} + +.ie9 .hidden-ie9 { + display: none !important; +} + +/******************** + GENERAL LAYOUT +*********************/ + +/*** +Header and header elements. +***/ + +.header.navbar { + width: 100%; + padding: 0 20px 0 20px; + margin: 0; + border: 0px; + padding: 0px; + box-shadow: none; + height: 42px; + min-height: 42px; +} + +.header.navbar.navbar-fixed-top { + z-index: 9995 !important; +} + +.header.navbar .navbar-brand { + display: inline-block; + margin-top: -1px; + margin-right: 0; + padding-left: 0; + padding-right: 0; + width: 225px; + height: 42px; +} + +.header.navbar .navbar-brand img { + margin-left: 20px; +} + +.header.navbar .navbar-brand.text-logo { + padding-left: 20px; + padding-top: 12px; +} + +.header.navbar .navbar-toggle { + margin: 8px 6px 4px 6px; + padding: 0; + padding-top:2px; + padding-bottom: 6px; + background-image: none; + filter:none; + box-shadow: none; + color: #fff; + border: 0; +} + +.header.navbar .navbar-toggle:hover { + text-decoration: none; + background: none; +} + +.header.navbar .navbar-nav { + margin-right: 20px; + display: block; +} + +.header.navbar .navbar-nav > li { + margin: 0px; + padding: 0px; +} + +.header.navbar .navbar-nav > li.dropdown, +.header.navbar .navbar-nav > li.dropdown > a { + padding-left: 4px; + padding-right: 4px; +} + +.header.navbar .navbar-nav > li.dropdown > a:last-child { + padding-right: 0; +} + +.header.navbar .navbar-nav > li.dropdown:last-child { + padding-right: 2px; +} + +.header.navbar .navbar-nav > li.dropdown .dropdown-toggle { + margin: 0px; + padding: 15px 10px 7px 10px; +} + +.header.navbar .navbar-nav > li.dropdown .dropdown-toggle > i { + font-size: 18px; +} + +.header.navbar .navbar-nav > li.dropdown .dropdown-menu > li > a > i { + font-size: 14px; +} + +.header.navbar .navbar-nav > li.dropdown.user .dropdown-toggle { + padding: 7px 0px 6px 6px; +} + +.header.navbar .navbar-nav > li.dropdown.user .dropdown-toggle:hover { + text-decoration: none; +} + +.header.navbar .navbar-nav > li.dropdown.user .dropdown-toggle .username { + color: #ddd; +} + +.header.navbar .navbar-nav > li.dropdown.user .dropdown-toggle i { + display: inline-block; + margin-top: 5px; + margin: 0; + font-size: 16px; +} + +.header.navbar .navbar-nav > li.dropdown.user .dropdown-menu i { + width: 15px; + display: inline-block; +} + +.header.navbar .navbar-nav > li.dropdown .dropdown-toggle .badge { + position: absolute; + top: 8px; + right: 20px; +} + +/*** +Header Search +***/ +.header.navbar .search-form { + float: left; + display: inline-block; + padding: 0; + height: 41px; + margin:0; +} + +.header.navbar .search-form .form-control{ + margin-top: 8px; + border: 0; + padding-top: 1px; + padding-right: 27px; +} + +.header.navbar .search-form .submit { + position: relative; + display: block; + float: right; + margin-top: -21px; + margin-right: 8px; + width: 13px; + height: 15px; + box-shadow: none; + border: 0px; + padding: 0px; + background-color: none; + background-repeat: no-repeat !important; + outline: none !important; + opacity: 0.8; + filter: alpha(opacity=80); +} + +.header.navbar .search-form .submit:hover { + opacity: 1; + filter: alpha(opacity=100); +} + +/*** +Language Bar +***/ + +.header.navbar .navbar-nav > li.dropdown.language { + padding-left: 0; + padding-right: 0; + margin: 0; +} + +.header.navbar .navbar-nav > li.dropdown.language > a { + color: #ddd; + font-size: 13px; + padding: 11px 1px 11px 5px; +} + +.header.navbar .navbar-nav > li.dropdown.language > a > img { + margin-bottom: 2px; +} + +.header.navbar .navbar-nav > li.dropdown.language > a > i { + font-size: 16px; +} + +.header.navbar .navbar-nav > li.dropdown.language > .dropdown-menu > li > a > img { + margin-bottom: 2px; +} + +.header.navbar .navbar-nav .dropdown-menu { + margin-top: 3px; +} + +/*** +Page container +***/ + +.page-container { + margin: 0px; + padding: 0px; + position: relative; +} + +.page-container:before, +.page-container:after { + display: table; + content: " "; +} + +.page-container:after { + clear: both; +} + +.page-header-fixed .page-container { + margin-top: 42px; +} + +/*** IE 8 Fixes ***/ +/*** +Page sidebar +***/ + +.ie8 .page-sidebar { + width: 225px; + float: left; + position: relative; + margin-right: -100%; +} + +/*** +Page content +***/ + +.ie8 .page-content-wrapper { + float: left; + width: 100%; + } + +.ie8 .page-content { + margin-left: 225px; + margin-top: 0px; + min-height: 760px; + padding: 25px 20px 20px 20px; + } +/*** IE 8 Fixes ***/ + +/*** +Page sidebar +***/ + +.page-sidebar.navbar-collapse { + padding: 0; +} + +.page-sidebar-menu { + list-style: none; + margin: 0; + padding: 0; + margin: 0; + padding: 0; +} + +.page-sidebar-menu > li { + display: block; + margin: 0; + padding: 0; + border: 0px; +} + +.page-sidebar-menu > li.start > a { + border-top-color: transparent !important; +} + +.page-sidebar-menu > li:last-child > a, +.page-sidebar-menu > li.last > a { + border-bottom-color: transparent !important; +} + +.page-sidebar-menu > li > a { + display: block; + position: relative; + margin: 0; + border: 0px; + padding: 10px 15px; + text-decoration: none; + font-size: 14px; + font-weight: 300; +} + +.page-sidebar-fixed .page-sidebar-menu > li > a { + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + -o-transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.page-sidebar-reversed.page-sidebar-fixed .page-sidebar-menu > li > a{ + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; +} + +.page-sidebar-menu > li > a i { + font-size: 16px; + margin-right: 5px; + text-shadow:none; +} + +.page-sidebar-menu > li.break { + margin-bottom: 20px; +} + +.page-sidebar-menu > li.open > a { + font-size: 14px; +} + +.page-sidebar-menu > li.active > a { + border: none; + text-shadow:none; + font-size: 14px; +} + +.page-sidebar-menu > li.active > a .selected { + display: block; + width: 8px; + height: 25px; + background-image: url("../img/sidebar-menu-arrow.png"); + float: right; + position: absolute; + right:0px; + top:8px; +} + +.page-sidebar-reversed .page-sidebar-menu > li.active > a .selected { + background-image: url("../img/sidebar-menu-arrow-reverse.png"); + right: auto; + left:0; +} + +.page-sidebar ul > li > a > .arrow:before { + float: right; + margin-top: 0px; + margin-right: 5px; + display: inline; + font-size: 16px; + font-family: FontAwesome; + height: auto; + content: "\f104"; + font-weight: 300; + text-shadow:none; +} + +.page-sidebar-menu > li > a > .arrow.open:before { + float: right; + margin-top: 0px; + margin-right: 3px; + display: inline; + font-family: FontAwesome; + height: auto; + font-size: 16px; + content: "\f107"; + font-weight: 300; + text-shadow:none; +} + +/* bagin: sidebar menu badges */ +.page-sidebar-menu li > a > .badge { + float: right; + margin-top: 1px; + margin-right: 13px; +} + +/* end: sidebar menu badges */ + +.page-sidebar-menu .sub-menu { + padding: 0; +} + +.page-sidebar-menu > li > ul.sub-menu { + display: none; + list-style: none; + clear: both; + margin: 8px 0px 8px 0px; +} + +.page-sidebar-menu > li.active > ul.sub-menu { + display: block; +} + +.page-sidebar-menu > li > ul.sub-menu > li { + background: none; + margin: 0px; + padding: 0px; + margin-top: 1px !important; +} + +.page-sidebar-menu > li > ul.sub-menu > li > a { + display: block; + margin: 0px 0px 0px 0px; + padding: 5px 0px; + padding-left: 44px !important; + text-decoration: none; + font-size: 14px; + font-weight: 300; + background: none; +} + +/* 3rd level sub menu */ +.page-sidebar-menu > li > ul.sub-menu > li ul.sub-menu { + display: none; + list-style: none; + clear: both; + margin: 0px 0px 0px 0px; +} + +.page-sidebar-menu > li > ul.sub-menu li > a > .arrow:before { + float: right; + margin-top: 1px; + margin-right: 20px; + display: inline; + font-size: 16px; + font-family: FontAwesome; + height: auto; + content: "\f104"; + font-weight: 300; + text-shadow:none; +} + +.page-sidebar-menu > li > ul.sub-menu li > a > .arrow.open:before { + float: right; + margin-top: 1px; + margin-right: 18px; + display: inline; + font-family: FontAwesome; + height: auto; + font-size: 16px; + content: "\f107"; + font-weight: 300; + text-shadow:none; +} + +.page-sidebar-menu > li.active > ul.sub-menu > li.active ul.sub-menu { + display: block; +} + +.page-sidebar-menu > li > ul.sub-menu > li ul.sub-menu li { + background: none; + margin: 0px; + padding: 0px; + margin-top: 1px !important; +} + +.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a { + display: block; + margin: 0px 0px 0px 0px; + padding: 5px 0px; + text-decoration: none; + font-size: 14px; + font-weight: 300; + background: none; +} + +.page-sidebar-menu > li > ul.sub-menu > li > ul.sub-menu > li > a { + padding-left: 60px; +} + +.page-sidebar-menu > li > ul.sub-menu > li > ul.sub-menu > li > ul.sub-menu > li > a { + padding-left: 80px; +} + +.page-sidebar-menu > li.active > ul.sub-menu > li.active ul.sub-menu > li.active ul.sub-menu { + display: block; +} + + +.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a > i { + font-size: 13px; +} + +/*** +Sidebar Search +***/ + +.page-sidebar .sidebar-search { + padding:0; + margin: 0; +} + +.page-sidebar .header.navbar-responsive-search { + display: none; +} + +.page-sidebar .sidebar-search .form-container { + margin: 15px 20px 15px 20px; + height: 35px; + padding-top: 7px; +} + +.page-sidebar .sidebar-search .form-container .submit { + display: block; + float: right; + margin-top: 3px; + width: 13px; + height: 15px; + background-repeat: no-repeat; + box-shadow: none; + border: 0px; + padding: 0px; + outline: none !important; +} + +.page-sidebar .sidebar-search .form-container input[type="text"] { + margin: 0px; + width: 165px; + border: 0px; + padding: 0 !important; + font-size: 14px !important; + box-shadow: none !important; + font-size: 14px; + font-weight: normal; +} + +.page-sidebar .sidebar-search .form-container input[type="text"]:focus { + outline: none !important; +} + +/*** +Sidebar toggler(show/hide) +***/ +.sidebar-toggler { + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); + width: 29px; + height: 29px; + background-repeat: no-repeat; +} + +.sidebar-toggler:hover { + filter: alpha(opacity=100); + opacity: 1; +} + +.page-sidebar .sidebar-toggler { + margin-top: 15px; + margin-left: 175px; +} + +.header.navbar .sidebar-toggler { + float: left; + display: inline-block; + margin-top: 6px; + margin-left: -42px; +} + +/*** +Page content +***/ +.page-content { + margin-top: 0px; + padding: 0px; + background-color: #fff; +} + +.ie8 .page-content { + padding: 20px; + margin-left: 225px; + margin-top: 0px; + min-height: 760px; +} + +.ie8 .page-sidebar-fixed .page-content { + min-height: 600px; +} + +.ie8 .page-content.no-min-height { + min-height: auto; +} + +.page-full-width .page-content { + margin-left: 0px !important; +} + +.page-full-width .page-sidebar-menu { + display: none; +} + +/*** +Page title +***/ +.page-title { + padding: 0px; + font-size: 30px; + letter-spacing: -1px; + display: block; + color: #666; + margin: 0px 0px 15px 0px; + font-weight: 300; + font-family: 'Open Sans', sans-serif; +} + +.page-title small { + font-size: 14px; + letter-spacing: 0px; + font-weight: 300; + color: #888; +} + +/*** +Page breadcrumb +***/ + +.ie8 .row .page-breadcrumb.breadcrumb > li { + margin-right: 1px; +} + +.page-content .page-breadcrumb.breadcrumb { + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; + box-shadow: none; + padding-right: 30px; + padding-left: 8px; + margin-top: 15px; + margin-bottom: 25px; + border:0px !important; + background-color: #eee; +} + +.page-content .page-breadcrumb.breadcrumb > li > a, +.page-content .page-breadcrumb.breadcrumb > li > i, +.page-content .page-breadcrumb.breadcrumb > li > span { + color: #333; + font-size: 14px; + text-shadow:none; +} + +.page-content .page-breadcrumb.breadcrumb > li > i { + color: #666; +} + +.page-content .page-breadcrumb.breadcrumb > li+li:before { + display: none; +} + +/* Dashboard breadcrumb Dropdown */ +.page-content .page-breadcrumb.breadcrumb .btn-group { + right: 15px; + position: absolute; + margin-top: -8px; +} + +.page-content .page-breadcrumb.breadcrumb > .btn-group .btn { + padding-top: 8px; + padding-bottom: 8px; +} + +/* Dashboard date range panel */ +.page-content .page-breadcrumb.breadcrumb .dashboard-date-range { + position: relative; + top: -8px; + margin-right: -30px; + display: none; + padding: 9px 9px 8px 9px; + cursor: pointer; + color: #fff; + background-color: #e02222; +} + +/* hack for chrome and safari */ +@media all and (-webkit-min-device-pixel-ratio:0) { + .page-content .page-breadcrumb.breadcrumb .dashboard-date-range { + padding: 9px; + } +} + +.page-content .page-breadcrumb.breadcrumb .dashboard-date-range > span { + font-size: 12px; + font-weight: 300; + color: #fff; + text-transform: uppercase; +} + +.page-content .page-breadcrumb.breadcrumb .dashboard-date-range > .fa-calendar { + text-transform: none; + color: #fff; + margin-top: 0px; + font-size: 14px; +} + +.page-content .page-breadcrumb.breadcrumb .dashboard-date-range > .fa-angle-down { + color:#fff; + font-size: 16px; +} + +/*** +Footer +***/ + +.footer { + padding: 8px 20px 5px 20px; + font-size: 12px; +} + +.footer:after, +.footer:before { + content: ""; + display: table; + line-height: 0; +} + +.footer:after { + clear: both; +} + +.footer .footer-inner { + float: left; + display: inline-block; +} + +.footer .footer-tools { + float: right; + display: inline-block; +} + +.footer .footer-tools .go-top { + display: block; + text-decoration: none; + cursor: pointer; + margin-top: -2px; + margin-right: 0px; + margin-bottom: 0px; + font-size: 16px; + padding: 0px 6px 0px 6px; +} + +.footer .footer-tools .go-top i { + font-size: 22px; + margin-bottom: 5px; +} + + +/******************** + GENERAL UI ELEMENTS +*********************/ + +/*** +Icon stuff +***/ +i.icon, a.icon { + color: #999; + margin-right: 5px; + font-weight: normal; + font-size: 13px; +} + +i.icon-black { + color: #000 !important; +} + +a.icon:hover { + text-decoration: none; + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -o-transition: all 0.1s ease-in-out; + -ms-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; + opacity: .4; + filter:alpha(opacity=40); +} + +a.icon.huge i{ + font-size: 16px !important; +} + +i.big { + font-size: 20px; +} + +i.warning { + color: #d12610; +} + +i.critical { + color: #37b7f3; +} + +i.normal { + color: #52e136; +} + +/*** +Custom wells +***/ +.well { + background-color: #fafafa; + border: 1px solid #eee; + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.well.mini { + padding: 7px !important; +} + +/*** +Form stuff +***/ + + +/*** +Bordered form layout +***/ + +/*** +Input icons +***/ + +/* input with right aligned and colored icons */ + +/* input with left aligned icons */ +.input-icon { + position: relative; +} + + +.input-icon input { + padding-left: 33px !important; +} + +.input-icon i { + color: #ccc; + display: block; + position: absolute; + margin: 11px 2px 4px 10px; + width: 16px; + height: 16px; + font-size: 16px; + text-align: center; +} + +.input-icon.right input { + padding-left: 12px !important; + padding-right: 33px !important; +} + +.input-icon.right i { + right: 8px; + float: right; +} + +.has-success .input-icon > i { + color: #468847; +} + +.has-warning .input-icon > i { + color: #c09853; +} + +.has-error .input-icon > i { + color: #b94a48; +} + +/*** +Portlets +***/ +.portlet { + clear: both; + margin-top: 0px; + margin-bottom: 25px; + padding: 0px; +} + +.portlet > .portlet-title { + margin-bottom: 15px; + border-bottom: 1px solid #eee; +} + +.portlet > .portlet-title:after, +.portlet > .portlet-title:before { + content: ""; + display: table; + line-height: 0; +} + +.portlet > .portlet-title:after { + clear: both; +} + +.portlet > .portlet-title > .caption { + float: left; + display: inline-block; + font-size: 18px; + line-height: 18px; + font-weight: 400; + margin: 0; + padding: 0; + margin-bottom: 8px; +} + +.portlet > .portlet-title > .caption > i { + float: left; + margin-top: 4px; + display: inline-block !important; + font-size: 13px; + margin-right: 5px; + color: #666; +} + +.portlet.blue > .portlet-title > .caption, +.portlet.green > .portlet-title > .caption, +.portlet.yellow > .portlet-title > .caption, +.portlet.red > .portlet-title > .caption, +.portlet.purple > .portlet-title > .caption, +.portlet.grey > .portlet-title > .caption { + color: #fff; +} + +.portlet.box.blue > .portlet-title > .caption > i, +.portlet.box.green > .portlet-title > .caption > i, +.portlet.box.grey > .portlet-title > .caption > i, +.portlet.box.yellow > .portlet-title > .caption > i, +.portlet.box.red > .portlet-title > .caption > i, +.portlet.box.purple > .portlet-title > .caption > i, +.portlet.box.light-grey > .portlet-title > .caption > i{ + color: #fff; +} + +.sortable .portlet > .portlet-title { + cursor: move; +} + +.portlet > .portlet-title > .tools, +.portlet > .portlet-title > .actions + { + display: inline-block; + padding: 0; + margin: 0; + margin-top: 6px; + float: right; +} + +.portlet > .portlet-title > .tools > a { + display: inline-block; + height: 16px; + margin-left:5px; +} + +.portlet > .portlet-title > .actions > .dropdown-menu i { + color: #000 !important; +} + +.portlet > .portlet-title > .tools > a.remove { + margin-bottom: 2px; + background-image:url(../img/portlet-remove-icon.png); + background-repeat: no-repeat; + width: 11px; +} + +.portlet > .portlet-title > .tools > a.config { + margin-bottom: 2px; + background-image:url(../img/portlet-config-icon.png); + background-repeat: no-repeat; + width: 12px; +} + +.portlet > .portlet-title > .tools > a.reload { + margin-bottom: 2px; + background-image:url(../img/portlet-reload-icon.png); + width: 13px; +} + +.portlet > .portlet-title > .tools > a.expand { + margin-bottom: 2px; + background-image:url(../img/portlet-expand-icon.png); + width: 14px; +} + +.portlet > .portlet-title > .tools > a.collapse { + margin-bottom: 2px; + background-image:url(../img/portlet-collapse-icon.png); + width: 14px; +} + +.portlet > .portlet-title > .tools > a:hover { + text-decoration: none; + -webkit-transition: all 0.1s ease-in-out; + -moz-transition: all 0.1s ease-in-out; + -o-transition: all 0.1s ease-in-out; + -ms-transition: all 0.1s ease-in-out; + transition: all 0.1s ease-in-out; + opacity:.6; + filter:'alpha(opacity=60)'; +} + +.portlet > .portlet-title > .actions > .btn-group { + margin-top: -13px; +} + +.portlet > .portlet-title > .actions > .btn { + padding: 4px 10px; + margin-top: -14px; +} + +.portlet > .portlet-title > .actions > .btn-group > .btn { + padding: 4px 10px; + margin-top: -1px; +} + +.portlet > .portlet-title > .actions > .btn.btn-sm { + padding: 3px 8px; + margin-top: -13px; +} + +.portlet > .portlet-title > .actions > .btn-group > .btn-sm { + padding: 3px 8px; + margin-top: -1px; +} + +.portlet > .portlet-title > .pagination.pagination-sm { + float: right !important; + display: inline-block !important; + margin: 0px; + margin-top: -4px; +} + +@media (max-width: 767px) { + .portlet > .portlet-title > .actions.btn-set > .btn-group, + .portlet > .portlet-title > .actions.btn-set > .btn { + margin-top: 0px; + margin-bottom: 5px; + } +} + +.portlet > .portlet-body { + clear: both; + padding: 0; +} + +.portlet > .portlet-empty { + min-height: 125px; +} + +.portlet > .portlet-body.light-blue, .portlet.light-blue { + background-color: #bfd5fa !important; +} + +.portlet > .portlet-body.blue, .portlet.blue { + background-color: #4b8df8 !important; +} + +.portlet > .portlet-body.red, .portlet.red { + background-color: #e02222 !important; +} + +.portlet > .portlet-body.yellow, .portlet.yellow { + background-color: #ffb848 !important; +} + +.portlet > .portlet-body.green, .portlet.green { + background-color: #35aa47 !important; +} + +.portlet > .portlet-body.purple, .portlet.purple { + background-color: #852b99 !important; +} + +.portlet > .portlet-body.light-grey, .portlet.light-grey { + background-color: #fafafa !important; +} + +.portlet > .portlet-body.grey, .portlet.grey { + background-color: #555555 !important; +} + +/* draggable girds */ + +.ui-sortable-placeholder { + border: 1px dotted black; + visibility: visible !important; + height: 100% !important; +} + +.ui-sortable-placeholder * { + visibility: hidden; +} + +.sortable-box-placeholder { + background-color: #f5f5f5; + border: 1px dashed #DDDDDD; + display: block; + /* float: left;*/ + margin-top: 0px !important; + margin-bottom: 24px !important; +} + +.sortable-box-placeholder * { + visibility:hidden; +} + +/*** +Solid colored portlet +***/ +.portlet.solid { + padding: 10px; +} + +.portlet.solid > .portlet-title > .tools { + margin-top: 2px; + border: 0px; +} + +.portlet.solid > .portlet-title { + margin-bottom: 5px; + border: 0px; +} + +.portlet.solid.bordered > .portlet-title { + margin-bottom: 15px; +} + +.portlet.solid.red > .portlet-title, +.portlet.solid.red > .portlet-title > .caption > i, +.portlet.solid.red > .portlet-body, + +.portlet.solid.green > .portlet-title, +.portlet.solid.green > .portlet-title > .caption > i, +.portlet.solid.green > .portlet-body, + +.portlet.solid.yellow > .portlet-title, +.portlet.solid.yellow > .portlet-title > .caption > i, +.portlet.solid.yellow > .portlet-body, + +.portlet.solid.grey > .portlet-title, +.portlet.solid.grey > .portlet-title > .caption > i, +.portlet.solid.grey > .portlet-body, + +.portlet.solid.purple > .portlet-title, +.portlet.solid.purple > .portlet-title > .caption > i, +.portlet.solid.purple > .portlet-body, + +.portlet.solid.blue > .portlet-title, +.portlet.solid.blue > .portlet-title > .caption > i, +.portlet.solid.blue > .portlet-body { + border: 0; + color: #fff; +} + +.portlet.bordered { + border-left: 2px solid #ddd; +} + +/*** +Box portlet +***/ + +.portlet.box { + padding:0px !important +} + +.portlet.box > .portlet-title { + padding:8px 10px 2px 10px; + border-bottom: 1px solid #eee; + color: #fff !important; +} + +.portlet.box > .portlet-title > .tools { + margin-top: 3px; +} + +.portlet.box > .portlet-title > .tools > a.remove, +.portlet.solid > .portlet-title > .tools > a.remove { + background-image:url(../img/portlet-remove-icon-white.png); +} + +.portlet.box > .portlet-title > .tools > a.config, +.portlet.solid > .portlet-title > .tools > a.config { + background-image:url(../img/portlet-config-icon-white.png); +} + +.portlet.box > .portlet-title > .tools > a.reload, +.portlet.solid > .portlet-title > .tools > a.reload { + background-image:url(../img/portlet-reload-icon-white.png); +} + +.portlet.box > .portlet-title > .tools > a.expand, +.portlet.solid > .portlet-title > .tools > a.expand { + background-image:url(../img/portlet-expand-icon-white.png); +} + +.portlet.box > .portlet-title > .tools > a.collapse, +.portlet.solid > .portlet-title > .tools > a.collapse { + background-image:url(../img/portlet-collapse-icon-white.png); +} + +/* portlet buttons */ +.portlet.box > .portlet-body { + background-color: #fff; + padding: 10px; +} + +.portlet.box > .portlet-title { + margin-bottom: 0px; +} + +.portlet.box.blue > .portlet-title { + background-color: #4b8df8; +} + +.portlet.box.blue { + border: 1px solid #b4cef8; + border-top: 0; +} + +.portlet.box.red > .portlet-title { + background-color: #e02222; +} + +.portlet.box.red { + border: 1px solid #ef8476; + border-top: 0; +} + +.portlet.box.yellow > .portlet-title { + background-color: #ffb848; +} + +.portlet.box.yellow { + border: 1px solid #fccb7e; + border-top: 0; +} + +.portlet.box.green > .portlet-title { + background-color: #35aa47; +} + +.portlet.box.green { + border: 1px solid #77e588; + border-top: 0; +} + +.portlet.box.purple > .portlet-title { + background-color: #852b99; +} + +.portlet.box.purple { + border: 1px solid #af5cc1; + border-top: 0; +} + +.portlet.box.grey > .portlet-title { + background-color: #555555; +} + +.portlet.box.grey { + border: 1px solid #9d9c9c; + border-top: 0; +} + +.portlet.box.light-grey > .portlet-title { + background-color: #aaa; +} + +.portlet.box.light-grey { + border: 1px solid #bbb; + border-top: 0; +} + +/*** +Charts and statistics +***/ +.chart, .pie, .bars { + overflow: hidden; + height: 300px; +} + +/*** +Statistic lists +***/ +.item-list.table .percent { + width: 30px; + float: right; + margin-right: 10px; + margin-top: 3px; +} + +/*** +Chart tooltips +***/ +.chart-tooltip { + clear: both; + z-index: 100; + background-color: #736e6e !important; + padding: 5px !important; + color: #fff; +} + +.chart-tooltip .label { + clear: both; + display: block; + margin-bottom: 2px; +} + +/*** +Mini chart containers +***/ +.bar-chart { + display: none +} + +.line-chart { + display: none +} + +/*** +Custom icon buttons +***/ +.icon-btn { + height: 60px; + min-width: 80px; + margin: 5px 5px 0 0; + border: 1px solid #ddd; + padding: 12px 0px 0px 0px; + background-color: #fafafa !important; + background-image: none !important; + filter:none !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; + display:inline-block !important; + color: #646464 !important; + text-shadow: none !important; + text-align: center; + cursor: pointer; + position: relative; + -webkit-transition: all 0.3s ease !important; + -moz-transition: all 0.3s ease !important; + -ms-transition: all 0.3s ease !important; + -o-transition: all 0.3s ease !important; + transition: all 0.3s ease !important; +} + +.icon-btn i { + font-size: 18px; +} + +.ie8 .icon-btn:hover { + filter: none !important; +} + +.icon-btn:hover { + text-decoration: none !important; + border-color: #999 !important; + color: #444 !important; + text-shadow: 0 1px 0px rgba(255, 255, 255, 1) !important; + -webkit-transition: all 0.3s ease !important; + -moz-transition: all 0.3s ease !important; + -ms-transition: all 0.3s ease !important; + -o-transition: all 0.3s ease !important; + transition: all 0.3s ease !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.icon-btn:hover .badge { + -webkit-transition: all 0.3s ease !important; + -moz-transition: all 0.3s ease !important; + -ms-transition: all 0.3s ease !important; + -o-transition: all 0.3s ease !important; + transition: all 0.3s ease !important; + -webkit-box-shadow: none !important; + -moz-box-shadow: none !important; + box-shadow: none !important; +} + +.icon-btn div { + font-family: 'Open Sans', sans-serif; + margin-top: 5px; + margin-bottom: 20px; + color: #000; + font-size: 12px; + font-weight: 300; +} + +.icon-btn .badge { + position: absolute; + font-family: 'Open Sans', sans-serif; + font-size: 11px !important; + font-weight: 300; + top: -5px; + right: -5px; + padding: 3px 6px 3px 6px; + color: white !important; + text-shadow: none; + border-width: 0; + border-style: solid; + -webkit-border-radius: 12px !important; + -moz-border-radius: 12px !important; + border-radius: 12px !important; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +/* extended dropdowns */ +.dropdown-menu.extended { + min-width: 160px !important; + max-width: 300px !important; + width: 233px !important; + background-color: #ffffff !important; +} + +.dropdown-menu.extended:before, +.dropdown-menu.extended:after { + border-bottom-color: #ddd !important; +} + +.dropdown-menu.extended li a{ + display: block; + padding: 5px 10px !important; + clear: both; + font-weight: normal; + line-height: 20px; + white-space: normal !important; +} + +.dropdown-menu.extended li i{ + margin-right: 3px; +} + +.dropdown-menu.extended li a{ + font-size: 13px; + padding: 10px !important; + background-color: #ffffff; +} + +.dropdown-menu.extended li a:hover { + background-image: none; + background-color: #f5f5f5; + color: #000; + filter:none; +} + +.dropdown-menu.extended li p{ + padding: 10px; + background-color: #eee; + margin: 0px; + font-size: 14px; + font-weight: 300; + color: #000; +} + +.dropdown-menu.extended li a{ + padding: 7px 0 5px 0px; + list-style: none; + border-bottom: 1px solid #f4f4f4 !important; + font-size: 12px; + text-shadow: none; +} + +.dropdown-menu.extended li:first-child a { + border-top: none; + border-bottom: 1px solid #f4f4f4 !important; +} + +.dropdown-menu.extended li:last-child a { + border-top: 1px solid white !important; + border-bottom: 1px solid #f4f4f4 !important; +} + +.dropdown-menu.extended li.external > a { + font-size: 13px; + font-weight: 400; +} + +.dropdown-menu.extended li.external > a > i{ + margin-top: 3px; + float: right; +} + +/* header notifications dropdowns */ +.dropdown-menu .dropdown-menu-list.scroller { + padding-right: 0 !important; + padding-left: 0; + list-style: none; +} + +.dropdown-menu.notification li > a .time { + font-size: 12px; + font-weight: 600; + text-align: right; + font-style: italic; +} + +/* header inbox dropdowns */ +.dropdown-menu.inbox li > a .photo { + float: left; + padding-right: 6px; +} + +.dropdown-menu.inbox li > a .photo > img { + height: 40px; + width: 40px; +} + +.dropdown-menu.inbox li > a .subject { + display: block; +} + +.dropdown-menu.inbox li > a .subject .from { + font-size: 14px; + font-weight: 400; + color: #02689b; +} + +.dropdown-menu.inbox li > a .subject .time { + font-size: 12px; + font-weight: 600; + font-style: italic; + position: relative; + float: right; +} + +.dropdown-menu.inbox li > a .message { + display: block !important; + font-size: 12px; +} + +/* header tasks */ +.dropdown-menu.tasks .task { + margin-bottom: 5px; +} + +.dropdown-menu.tasks .task .desc { + font-size: 13px; + font-weight: 300; +} + +.dropdown-menu.tasks .task .percent { + font-size: 14px; + font-weight: 600; + font-family: 'Open Sans', sans-serif; + float: right; + display: inline-block; +} + +.dropdown-menu.tasks .progress { + display: block; + height: 11px; + margin: 0px; +} + +/*** +General list for item with image +***/ +.item-list li .img { + height: 50px; + width: 50px; + float: left; + margin-top: 3px; + margin-right: 5px; +} + +.item-list { + margin: 0px; + list-style: none; +} + +.item-list li { + padding: 7px 0 5px 0px; + list-style: none; + border-top: 1px solid white; + border-bottom: 1px solid #EBEBEB; + font-size: 12px; +} + +.item-list li:first-child { + border-top: none; + border-bottom: 1px solid #EBEBEB; +} + +.item-list li:last-child { + border-top: none; + border-bottom: none; +} + +.item-list li .label { + margin-right: 5px; +} + +.item-list.todo li .label { + position: absolute; + right: 80px; +} + +.item-list.todo li .actions { + position: absolute; + right: 45px; +} + +/*** +Custom tables +***/ +.table-toolbar { + margin-bottom: 15px; +} + +.table.table-full-width { + width: 100% !important; +} + +.table .m-btn { + margin-top: 0px; + margin-left: 0px; + margin-right: 5px; +} + +.table thead tr th { + font-size: 14px; + font-weight: 600; +} + +.table-advance { + margin-bottom: 10px !important; +} + +.table-advance thead { + color: #999; +} + +.table-advance thead tr th{ + background-color: #DDD; + font-size: 14px; + font-weight: 400; + color: #666; +} + +.table-advance div.success, +.table-advance div.info, +.table-advance div.important, +.table-advance div.warning, +.table-advance div.danger { + position: absolute; + margin-top:-5px; + float: left; + width: 2px; + height: 30px; + margin-right: 20px !important; +} + +.table-advance tr td { + border-left-width: 0px; +} +.table-advance tr td:first-child { + border-left-width: 1px !important; +} + +.table-advance tr td.highlight:first-child a { + margin-left: 15px; +} + +.table-advance td.highlight div.success { + border-left: 2px solid #66ee66; +} + +.table-advance td.highlight div.info { + border-left: 2px solid #87ceeb; +} + +.table-advance td.highlight div.important { + border-left: 2px solid #f02c71; +} + +.table-advance td.highlight div.warning { + border-left: 2px solid #fdbb39; +} + +.table-advance td.highlight div.danger { + border-left: 2px solid #e23e29; +} + +/*** +Star rating +***/ +.rating { + unicode-bidi: bidi-override; + direction: rtl; + font-size: 30px; +} + +.rating span.star { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + display: inline-block; +} + +.rating span.star:hover { + cursor: pointer; +} + +.rating span.star:before { + content: "\f006"; + padding-right: 5px; + color: #999999; +} + +.rating span.star:hover:before, +.rating span.star:hover ~ span.star:before { + content: "\f005"; + color: #e3cf7a; +} + + +/*** +Item block with details shown on hover +***/ +.item { + overflow: hidden; + display: block; + margin-bottom: 20px; +} + +.item .details { + width: 100%; + display: none; + background-color: #000; + color: #fff !important; + padding: 5px; + text-align: center; + position: relative; + bottom:30px; + margin-bottom:-30px; + overflow: hidden; + z-index: 6; +} + +.item:hover .details { + display: block; + opacity: 0.7; + filter: alpha(opacity = 70); +} + +.item:hover .zoom-icon{ + opacity:0.5; + filter: alpha(opacity = 50); +} + +/*** +Zoom icon overlay on images +***/ +.zoom { + cursor: pointer; + width: 100%; + height: 100%; + position: relative; + z-index: 5; +} + +.zoom .zoom-icon { + background-image:url("../img/overlay-icon.png"); + background-color: #222; + background-repeat: no-repeat; + background-position: 50%; + position: absolute; + width: inherit; + height: inherit; + opacity: 0; + filter: alpha(opacity = 0); + z-index: 6; + top:0; +} + +/*** +Chats +***/ +.chats { + margin:0; + padding: 0; + margin-top: -15px; +} + +.chats li { + list-style: none; + padding: 5px 0; + margin: 10px auto; + font-size: 12px; +} + +.chats li img.avatar { + height: 45px; + width: 45px; + -webkit-border-radius: 50% !important; + -moz-border-radius: 50% !important; + border-radius: 50% !important; +} + +.chats li.in img.avatar { + float: left; + margin-right: 10px; +} + +.chats li .name { + color:#3590c1; + font-size: 13px; + font-weight: 400; +} + +.chats li .datetime { + color:#333; + font-size: 13px; + font-weight: 400; +} + +.chats li.out img.avatar { + float: right; + margin-left: 10px; +} + +.chats li .message { + display: block; + padding: 5px; + position: relative; +} + +.chats li.in .message { + text-align: left; + border-left: 2px solid #35aa47; + margin-left: 65px; + background: #fafafa +} + +.chats li.in .message .arrow { + display: block; + position: absolute; + top: 5px; + left: -8px; + width: 0; + height: 0; + + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-right: 8px solid #35aa47; +} + +.chats li.out .message .arrow { + display: block; + position: absolute; + top: 5px; + right: -8px; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-left: 8px solid #da4a38; +} + +.chats li.out .message { + border-right: 2px solid #da4a38; + margin-right: 65px; + background: #fafafa; + text-align: right; +} + +.chats li.out .name, +.chats li.out .datetime { + text-align: right; +} + +.chats li .message .body { + display: block; +} + +.chat-form { + margin-top: 15px; + padding: 10px; + background-color: #e9eff3; + overflow: hidden; + clear: both; +} + +.chat-form .input-cont { + margin-right: 40px; +} + +.chat-form .input-cont .form-control { + width: 100% !important; + margin-bottom: 0px; +} + +.chat-form .input-cont input{ + border: 1px solid #ddd; + width: 100% !important; + margin-top: 0; +} + +.chat-form .input-cont input { + background-color: #fff !important; +} + +.chat-form .input-cont input:focus{ + border: 1px solid #4b8df9 !important; +} + +.chat-form .btn-cont { + margin-top: -42px; + position: relative; + float: right; + width:44px; +} + +.chat-form .btn-cont .arrow { + position: absolute; + top: 17px; + right: 43px; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + border-right: 8px solid #4d90fe; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.chat-form .btn-cont:hover .arrow { + border-right-color: #0362fd; +} + +.chat-form .btn-cont:hover .btn { + background-color: #0362fd; +} + +.chat-form .btn-cont .btn { + margin-top: 8px; +} + +/*** +System feeds +***/ +.feeds { + margin: 0px; + padding: 0px; + list-style: none; +} + +.feeds li { + background-color: #fafafa; + margin-bottom: 7px; +} + +.feeds li:before, +.feeds li:after { + display: table; + line-height: 0; + content: ""; +} + +.feeds li:after { + clear: both; +} + +.feeds li:last-child { + margin-bottom: 0px; +} + +.feeds .col1 { + float:left; + width:100%; + clear: both; +} + +.feeds .col2 { + float:left; + width:75px; + margin-left:-75px; +} + +.feeds .col1 .cont { + float:left; + margin-right:75px; + overflow:hidden; +} + +.feeds .col1 .cont .cont-col1 { + float:left; + margin-right:-100%; +} + +.feeds .col1 .cont .cont-col1 .label { + display: inline-block; + padding: 5px 4px 6px 5px; + vertical-align: middle; + text-align: center; +} +.feeds .col1 .cont .cont-col1 .label > i { + text-align: center; + font-size: 14px; +} + +.feeds .col1 .cont .cont-col2 { + float:left; + width:100%; +} + +.feeds .col1 .cont .cont-col2 .desc { + margin-left:35px; + padding-top: 4px; + padding-bottom: 5px; + overflow:hidden; +} + +.feeds .col2 .date { + padding: 4px 9px 5px 4px; + text-align: right; + font-style: italic; + color:#c1cbd0; +} + +/*** +Users +***/ +.user-info { + margin-bottom: 10px !important; +} + +.user-info img { + float: left; + margin-right: 5px; +} + +.user-info .details { + display: inline-block; +} + +.user-info .label { + font-weight: 300; + font-size: 11px; +} + +/*** +Accordions +***/ +.accordion-heading { + background:#eee; +} + +.accordion-heading a { + text-decoration:none; +} + +.accordion-heading a:hover { + text-decoration:none; +} + +/*** +Vertical inline menu +***/ +.ver-inline-menu { + padding: 0; + margin: 0; + list-style: none; +} + +.ver-inline-menu li { + position:relative; + margin-bottom:1px; +} + +.ver-inline-menu li i { + width: 37px; + height: 37px; + display: inline-block; + color:#b9cbd5; + font-size:15px; + padding:12px 10px 10px 8px; + margin:0 8px 0 0; + text-align: center; + background:#e0eaf0 !important; +} + +.ver-inline-menu li a { + font-size: 13px; + color:#557386; + display:block; + background:#f0f6fa; + border-left:solid 2px #c4d5df; +} + +.ver-inline-menu li:hover a, +.ver-inline-menu li:hover i { + background:#e0eaf0; + text-decoration:none; +} + +.ver-inline-menu li:hover i { + color:#fff; + background:#c4d5df !important; +} + +.ver-inline-menu li.active a, +.ver-inline-menu li:hover a { + font-size: 13px; +} + +.ver-inline-menu li.active a { + border-left:solid 2px #0c91e5; +} + +.ver-inline-menu li.active a, +.ver-inline-menu li.active i { + color:#fff; + background:#169ef4; + text-decoration:none; +} + +.ver-inline-menu li.active i { + background:#0c91e5 !important; +} + +.ver-inline-menu li.active:after { + content: ''; + display: inline-block; + border-bottom: 6px solid transparent; + border-top: 6px solid transparent; + border-left: 6px solid #169ef4; + position: absolute; + top: 12px; + right: -5px; +} + +/*** +Custom tabs +***/ +.nav-tabs > li > a > .badge, +.nav-pills > li > a > .badge { + margin-top: -3px; +} + +.nav-tabs > li > a, +.nav-pills > li > a { + font-size: 14px; +} + +.nav-tabs-sm > li > a, +.nav-pills-sm > li > a { + font-size: 13px; +} + +.tabbable-custom { + margin-bottom: 15px; + padding: 0px; + overflow: hidden; +} + +.tabbable-custom > .nav-tabs { + border: none; + margin: 0px; +} + +.tabbable-custom > .tab-content { + background-color: #fff; + border: 1px solid #ddd; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; + padding: 10px; +} + +.tabbable-custom.nav-justified .tab-content { + margin-top: -1px; +} + +.tabs-below.tabbable-custom.nav-justified .tab-content { + margin-top: 0px; + margin-bottom: -2px; +} + +.tabbable-custom.boxless > .tab-content { + padding:15px 0; + border-left:none; + border-right:none; + border-bottom:none; +} + +.tabbable-custom .nav-tabs > li { + margin-right: 2px; + border-top: 2px solid transparent; +} + +.tabbable-custom .nav-tabs > li > a { + margin-right: 0; + } + +.tabbable-custom .nav-tabs > li > a:hover { + background: none; + border-color:transparent; +} + +.tabbable-custom .nav-tabs > li.active { + border-top: 3px solid #d12610; + margin-top: 0; + position: relative; +} + +.tabbable-custom .nav-tabs > li.active > a { + border-top: none; + font-weight: 400; +} + +.tabbable-custom .nav-tabs > li.active > a:hover { + border-top: none; + background: #fff; + border-color: #d4d4d4 #d4d4d4 transparent; +} + +.tabbable-custom .nav-tabs > li { + margin-right: 2px; + border-top: 2px solid transparent; +} + +/* below tabs */ + +.tabs-below.tabbable-custom .nav-tabs > li > a { + border-top: none; + border-bottom: 2px solid transparent; + margin-top: -1px; +} + +.tabs-below.tabbable-custom .nav-tabs > li.active { + border-top: none; + border-bottom: 3px solid #d12610; + margin-bottom: 0; + position: relative; +} + +.tabs-below.tabbable-custom .nav-tabs > li.active > a { + border-bottom: none +} + +.tabs-below.tabbable-custom .nav-tabs > li.active > a:hover { + background: #fff; + border-color: #d4d4d4 #d4d4d4 transparent; +} + +/*full width tabs with bigger titles */ +.tabbable-custom.tabbable-full-width > .tab-content { + padding:15px 0; + border-left:none; + border-right:none; + border-bottom:none; +} + +.tabbable-custom.tabbable-full-width .nav-tabs > li > a { + color:#424242; + font-size:15px; + padding:9px 15px; +} + +/*** +Custom portlet tabs +***/ + +.portlet-tabs > .nav-tabs { + position: relative; + top: -41px; + margin-right: 10px; + overflow: hidden; +} + +.portlet-tabs > .nav-tabs > li { + float: right; +} + +.portlet-tabs > .nav-tabs { + border-bottom: none; +} + +.portlet-tabs > .nav-tabs > li > a { + color: #fff; + padding-top: 8px; + padding-bottom: 10px; + line-height: 16px; + margin-top: 6px; + margin-left: 0px; + margin-right: 0px; + border-left: 0; + border-right: 0; + -webkit-border-radius: 0px; + -moz-border-radius: 0px; + border-radius: 0px; +} + +.portlet-tabs > .nav-tabs > li:last-child > a { + border-right:0; +} + +.portlet-tabs > .nav-tabs > li { + margin-left: 1px; +} + +.portlet-tabs > .nav-tabs > li.active { + color: #333; + border-top-color: transparent; +} + +.portlet-tabs > .nav-tabs > li.active > a { + margin-bottom: 0px; + border-bottom: 0; + margin-left: 0px; + margin-right: 0px; + border-left: 0; + border-right: 0; + border-top-color:transparent !important; +} + +.portlet-tabs > .nav-tabs > li > a:hover { + color: #333; + margin-bottom: 0; + border-bottom-color: transparent; + margin-left: 0; + margin-right: 0; + border-left: 0; + border-right: 0; + border-top-color:transparent; + background-color: #fff; +} + +.portlet-tabs > .nav-tabs > .active > a { + color: #555555; + cursor: default; + background-color: #fff; +} + +.portlet-tabs > .nav-tabs > .active > a:hover { + background-color: #fff !important; +} + +.portlet-tabs > .tab-content { + padding: 10px !important; + margin: 0px; + margin-top: -50px !important; +} + +.portlet.tabbable .portlet-body { + padding: 0px; +} + +.tab-pane > p:last-child { + margin-bottom: 0px; +} + +/* reverse aligned tabs */ + +.tabs-reversed > li { + float: right; +} + +.tabs-reversed > li, +.tabs-reversed > li > a { + margin-right: 0; +} + +/*** +Dashboard container +***/ + +#dashboard { + overflow: hidden; +} + +/*** +Dashboard stats +***/ +.dashboard-stat { + margin-bottom: 25px; +} + +.portlet .dashboard-stat:last-child { + margin-bottom: 0; +} + +.dashboard-stat:before, +.dashboard-stat:after { + display: table; + line-height: 0; + content: ""; +} +.dashboard-stat:after { + clear: both; +} + +.dashboard-stat .visual { + width: 80px; + height:80px; + display: block; + float: left; + padding-top: 10px; + padding-left: 15px; + margin-bottom: 10px; +} + +.dashboard-stat .visual i { + font-size: 65px; + line-height: 65px; + color: #fff; +} + +.dashboard-stat .visual { + font-size: 35px; + line-height: 35px; +} + +@media (min-width: 992px) and (max-width: 1024px) { + + .dashboard-stat .visual i { + font-size: 28px; + line-height: 28px; + } + +} + +.dashboard-stat .details { + position: absolute; + right: 15px; + padding-right: 10px; +} + +.dashboard-stat .details .number { + padding-top: 15px; + text-align: right; + font-size: 34px; + line-height: 34px; + letter-spacing: -1px; + margin-bottom: 5px; + font-weight: 300; + color: #fff; +} + +.dashboard-stat .details .desc { + text-align: right; + font-size: 16px; + letter-spacing: 0px; + font-weight: 300; + color: #fff; +} + +.dashboard-stat .more { + clear: both; + display: block; + padding: 5px 10px 5px 10px; + text-transform: uppercase; + font-weight: 300; + font-size: 11px; + color: #fff; + opacity: 0.7; + filter: alpha(opacity=70); +} + +.dashboard-stat .more:hover { + text-decoration: none; + opacity: 1; + filter: alpha(opacity=100); +} + +.dashboard-stat .more > i { + display: inline-block; + margin-top: 1px; + float: right; +} + +.dashboard-stat.blue { + background-color: #27a9e3; +} + +.dashboard-stat.blue .more { + background-color: #208dbe; +} + +.dashboard-stat.green { + background-color: #28b779; +} + +.dashboard-stat.green .more { + background-color: #10a062; +} + +.dashboard-stat.red { + background-color: #e7191b; +} + +.dashboard-stat.red .more { + background-color:#bc0d0e; +} + +.dashboard-stat.yellow { + background-color: #ffb848; +} + +.dashboard-stat.yellow .more { + background-color: #cb871b; +} + +.dashboard-stat.purple { + background-color: #852b99; +} + +.dashboard-stat.purple .more { + background-color: #6e1881; +} + +/*** +Text Stats +***/ + +.text-stat h3 { + margin-top: 5px; + margin-bottom: 0px; + font-size: 18px; +} + +.text-stat span { + font-size: 12px; + text-transform: uppercase; +} + +@media (max-width: 767px) { + + .text-stat { + margin-top: 20px; + } + +} + +/*** +Tiles(new in v1.1.1) +***/ + +.tiles { + margin-right: -10px; +} + +.tiles:before, +.tiles:after { + display: table; + content: " "; +} + +.tiles:after { + clear: both; +} + +.tile { + display: block; + letter-spacing: 0.02em; + float: left; + height: 135px; + width: 135px !important; + cursor: pointer; + text-decoration: none; + color: #ffffff; + position: relative; + font-weight: 300; + font-size: 12px; + letter-spacing: 0.02em; + line-height: 20px; + overflow: hidden; + border: 4px solid transparent; + margin: 0 10px 10px 0; +} + +.tile:after, +.tile:before { + content: ""; + float: left; +} + +.tile.double { + width: 280px !important; +} + +.tile.double-down { + height: 280px !important; +} + +.tile:active, .tile.selected { + border-color: #ccc !important; +} + +.tile:hover { + border-color: #aaa !important; +} + +.tile.selected .corner:after { + content: ""; + display: inline-block; + border-left: 40px solid transparent; + border-bottom: 40px solid transparent; + border-right: 40px solid #ccc; + position: absolute; + top: -3px; + right: -3px; +} + +.tile.selected .check:after { + content: ""; + font-family: FontAwesome; + font-size: 13px; + content: "\f00c"; + display: inline-block; + position: absolute; + top: 2px; + right: 2px; +} + +.tile * { + color: #ffffff; +} + +.tile .tile-body { + height: 100%; + vertical-align: top; + padding: 10px 10px; + overflow: hidden; + position: relative; + font-weight: 400; + font-size: 12px; + color: #000000; + color: #ffffff; + margin-bottom: 10px; +} + +.tile .tile-body img { + float: left; + margin-right: 10px; +} + +.tile .tile-body img.pull-right { + float: right !important; + margin-left: 10px; + margin-right: 0px; +} + +.tile .tile-body .content { + display: inline-block; +} + +.tile .tile-body > i { + margin-top: 17px; + display: block; + font-size: 56px; + line-height: 56px; + text-align: center; +} + + +.tile.double-down i { + margin-top: 95px; +} + +.tile .tile-body h1, +.tile .tile-body h2, +.tile .tile-body h3, +.tile .tile-body h4, +.tile .tile-body h5, +.tile .tile-body h6, +.tile .tile-body p { + padding: 0; + margin: 0; + line-height: 14px; +} + +.tile .tile-body h3, +.tile .tile-body h4 { + margin-bottom: 5px; +} + +.tile .tile-body h1:hover, +.tile .tile-body h2:hover, +.tile .tile-body h3:hover, +.tile .tile-body h4:hover, +.tile .tile-body h5:hover, +.tile .tile-body h6:hover, +.tile .tile-body p:hover { + color: #ffffff; +} + +.tile .tile-body p { + font-weight: 400; + font-size: 13px; + color: #000000; + color: #ffffff; + line-height: 20px; + overflow: hidden; +} + +.tile .tile-body p:hover { + color: rgba(0, 0, 0, 0.8); +} + +.tile .tile-body p:active { + color: rgba(0, 0, 0, 0.4); +} + +.tile .tile-body p:hover { + color: #ffffff; +} + +.tile.icon > .tile-body { + padding: 0; +} + +.tile .tile-object { + position: absolute; + bottom: 0; + left: 0; + right: 0; + min-height: 30px; + background-color: transparent; + *zoom: 1; +} + +.tile .tile-object:before, +.tile .tile-object:after { + display: table; + content: ""; +} + +.tile .tile-object:after { + clear: both; +} + +.tile .tile-object > .name { + position: absolute; + bottom: 0; + left: 0; + margin-bottom: 5px; + margin-left: 10px; + margin-right: 15px; + font-weight: 400; + font-size: 13px; + color: #ffffff; +} + +.tile .tile-object > .name > i { + vertical-align: middle; + display: block; + font-size: 24px; + height: 18px; + width: 24px; +} + +.tile .tile-object > .number { + position: absolute; + bottom: 0; + right: 0; + margin-bottom: 0; + color: #ffffff; + text-align: center; + font-weight: 600; + font-size: 14px; + letter-spacing: 0.01em; + line-height: 14px; + margin-bottom: 8px; + margin-right: 10px; +} + +.tile.image > .tile-body { + padding: 0 !important; +} + +.tile.image > .tile-body > img{ + width: 100%; + height: auto; + min-height: 100%; + max-width: 100%; +} + +.tile.image .tile-body h3 { + display: inline-block; +} + +/*** +Theme Panel +***/ + +.theme-panel { + width: 400px; + margin-top: -20px; + margin-right: 1px; + z-index: 999; + float: right; + position:relative; +} + +.theme-panel > .toggler { + top:4px; + right:0; + padding:20px; + cursor:pointer; + position:absolute; + background:#c9c9c9 url(../img/icon-color.png) center no-repeat; +} + +.theme-panel > .toggler:hover { + background-color: #3d3d3d !important; +} + +.theme-panel > .toggler-close { + display: none; + top:4px; + right:0; + padding:20px; + cursor:pointer; + position:absolute; + background: #3d3d3d url(../img/icon-color-close.png) center no-repeat !important; +} + +.theme-panel > .toggler-close:hover { + background-color:#222 !important; +} + +.theme-panel > .theme-options { + top:4px; + right:40px; + display:none; + position:absolute; + background:#3d3d3d; +} + +.theme-panel > .theme-options > .theme-option { + color:#cfcfcf; + padding: 15px; + border-top:1px solid #585858; + margin-top: 0px; + margin-bottom: 0px; +} + +.theme-panel > .theme-options > .theme-option.theme-colors { + border-top: 0; +} + +.theme-panel > .theme-options > .theme-option > span { + text-transform:uppercase; + display: inline-block; + width: 138px; + font-size: 14px; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > span { + display: block; + width: auto; +} + +.theme-panel > .theme-options > .theme-option > select.form-control { + display: inline; + width: 100px; + text-transform: lowercase; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul { + list-style:none; + padding: 0; + display: block; + margin-bottom: 1px !important; + margin-top: 10px; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li { + width:37px; + height:37px; + margin:0 4px; + cursor:pointer; + list-style:none; + float: left; + border:solid 1px #707070; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li:first-child { + margin-left: 0; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li:hover, +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.current { + border:solid 2px #ebebeb; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.color-black { + background:#333438; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.color-grey { + background:#6d6d6d; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.color-blue { + background:#124f94; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.color-brown { + background:#623f18; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.color-purple { + background:#701584; +} + +.theme-panel > .theme-options > .theme-option.theme-colors > ul > li.color-white { + background:#fff; +} + +/*** +Top bar menu +***/ + +/* enable arrow for dropdown menu */ +.header.navbar .nav > li > .dropdown-menu:before { + position: absolute; + top: -7px; + right: 9px; + display: inline-block !important; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.header.navbar .nav > li > .dropdown-menu:after { + position: absolute; + top: -6px; + right: 10px; + display: inline-block !important; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; + content: ''; +} + +/*** +Mega Menu(new in v1.6) +***/ + +.mega-menu .nav, +.mega-menu .collapse, +.mega-menu .mega-menu-dropup, +.mega-menu .mega-menu-dropdown { + position: static; +} +.mega-menu .container { + position: relative; +} +.mega-menu .mega-menu-dropdown .dropdown-menu { + left: auto; + width: auto; +} +.mega-menu .nav.navbar-right .dropdown-menu { + left: auto; + right: 0; +} +.mega-menu .mega-menu-content { + padding: 10px; + margin: 0; +} +.mega-menu .mega-menu-full .dropdown-menu { + left: 20px; + right: 20px; +} + +.mega-menu-responsive-content { + padding: 10px 15px 10px 60px; +} + +.page-boxed .mega-menu .mega-menu-dropdown .dropdown-menu { + top: 42px; +} + +.page-boxed .mega-menu .mega-menu-dropdown.mega-menu-full .dropdown-menu { + margin: 0; + padding: 0; + left: 18px; + right: 18px; +} + +.mega-menu .mega-menu-submenu { + width: auto !important; + padding: 0px 15px !important; + margin: 0 !important; +} + +.mega-menu .mega-menu-submenu:last-child { + border-right: 0; +} + +.mega-menu .mega-menu-submenu li > h3 { + font-size: 14px; + margin-top: 10px; + padding-left: 5px; +} + +.mega-menu .mega-menu-submenu li { + padding: 2px !important; + margin: 0 !important; + list-style: none; +} + +.mega-menu .mega-menu-submenu li > a { + padding: 5px !important; + margin: 0 !important; +} + +/*** +Horezantal Menu(new in v1.2) +***/ + +.header.navbar .hor-menu { + margin: 0; + float: left; +} + +.header.navbar .hor-menu ul.nav li > a { + font-size: 14px; + padding: 11px 10px; +} + +.header.navbar .hor-menu ul.nav li.current .selected, +.header.navbar .hor-menu ul.nav li.active .selected { + left: 50%; + bottom:0; + position: absolute; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 6px solid #e02222; + display: inline-block; + margin: 0; + width: 0px; + height:0px; + margin-left: -7px; + margin-bottom:-6px; +} + +/*drop-down*/ +.header.navbar .hor-menu .dropdown-menu { + margin-top: 0; + border: none; + box-shadow: none; +} + +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-submenu > .dropdown-menu { + top: 0; +} + +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-submenu > a:after { + top: 8px; + margin-right: 0px; +} + +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-menu li > a { + padding: 7px 18px !important; + margin-bottom:1px; +} + +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-menu .arrow { + display: none; +} + +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-menu li > a:hover, +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-menu li:hover > a, +.header.navbar .hor-menu .classic-menu-dropdown .dropdown-menu li.active > a { + filter:none !important; +} + +.header.navbar .hor-menu .nav > li > .dropdown-menu:after, +.header.navbar .hor-menu .nav > li > .dropdown-menu:before { + border-bottom: none !important; +} + +/*search*/ +.header.navbar .hor-menu .hor-menu-search-form-toggler { + display: inline-block; + padding: 12px 22px 12px 22px !important; + cursor: pointer; + background: url(../img/hor-menu-search.png) no-repeat center; +} + +.header.navbar .hor-menu .hor-menu-search-form-toggler:hover { + opacity: 0.8; + filter: alpha(opacity=80); +} + +.header.navbar .hor-menu a.hor-menu-search-form-toggler-close { + display: none; +} + +.header.navbar .hor-menu .search-form { + margin: 0; + top:42px; + right:0px; + padding:0 4px; + display:none; + z-index:999; + position:absolute; +} + +.header.navbar .hor-menu .search-form .btn { + padding: 7px 20px; + height: 32px; + width: 10px; + display: inline-block; +} + +.header.navbar .hor-menu .search-form .btn:hover { + opacity: 0.8; + filter: alpha(opacity=80); +} + +.header.navbar .hor-menu .search-form form { + margin-bottom: 0; +} + +.header.navbar .hor-menu .search-form form input { + background: none; + width: 200px; + border: none; + margin-top: 6px; +} + +/*** +Top News Blocks(new in v1.2.2) +***/ +.top-news { + color: #fff; + margin: 8px 0; +} + +.top-news a, +.top-news em, +.top-news span { + display: block; + text-align: left; +} + +.top-news a { + padding: 10px; + position: relative; + margin-bottom: 10px; +} + +.top-news a .top-news-icon { + right: 8px; + bottom: 15px; + opacity:0.3; + font-size: 35px; + position: absolute; + filter: alpha(opacity=30); /*For IE8*/ +} + +.top-news em { + margin-bottom: 0; + font-style: normal; +} + +.top-news span { + font-size: 18px; + margin-bottom: 5px; +} + +/*** +Block Images(new in v1.2.2) +***/ +.blog-images { + margin-bottom: 0; +} + +.blog-images li { + padding: 0; + margin: 0; + display: inline; +} + +.blog-images li a:hover { + text-decoration: none; +} + +.blog-images li img { + width: 50px; + height: 50px; + opacity: 0.6; + margin: 0 2px 8px; +} + +.blog-images li img:hover { + opacity: 1; + box-shadow: 0 0 0 4px #72c02c; + transition: all 0.4s ease-in-out 0s; + -moz-transition: all 0.4s ease-in-out 0s; + -webkit-transition: all 0.4s ease-in-out 0s; +} + +/*Sidebar Tags*/ +ul.sidebar-tags a { + color: #555; + font-size:12px; + padding:3px 5px; + background:#f7f7f7; + margin:0 2px 5px 0; + display:inline-block; +} + +ul.sidebar-tags a:hover, +ul.sidebar-tags a:hover i { + background: #EEE; + text-decoration:none; + -webkit-transition:all 0.3s ease-in-out; + -moz-transition:all 0.3s ease-in-out; + -o-transition:all 0.3s ease-in-out; + transition:all 0.3s ease-in-out; +} + +ul.sidebar-tags a i { + color:#777; +} + +ul.sidebar-tags li { + padding: 0; +} + +/*** +Social Icons(new in v1.2.2) +***/ +.social-icons { + padding: 0; + margin:0; +} + +.social-icons:after, +.social-icons:before { + content: ""; + display: table; +} + +.social-icons:after { + clear: both; +} + +.social-icons li { + float:left; + display:inline; + list-style:none; + margin-right:5px; + margin-bottom:5px; + text-indent:-9999px; +} +.social-icons li a, a.social-icon { + width:28px; + height:28px; + display:block; + background-position:0 0; + background-repeat:no-repeat; + transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -webkit-transition: all 0.3s ease-in-out; +} +.social-icons li:hover a { + background-position:0 -38px; +} + +.social-icons-color li a { + opacity: 0.7; + background-position:0 -38px !important; +} + +.social-icons-color li a:hover { + opacity: 1; +} + +.social-icons .amazon {background: url(../img/social/amazon.png) no-repeat;} +.social-icons .behance {background: url(../img/social/behance.png) no-repeat;} +.social-icons .blogger {background: url(../img/social/blogger.png) no-repeat;} +.social-icons .deviantart {background: url(../img/social/deviantart.png) no-repeat;} +.social-icons .dribbble {background: url(../img/social/dribbble.png) no-repeat;} +.social-icons .dropbox {background: url(../img/social/dropbox.png) no-repeat;} +.social-icons .evernote {background: url(../img/social/evernote.png) no-repeat;} +.social-icons .facebook {background: url(../img/social/facebook.png) no-repeat;} +.social-icons .forrst {background: url(../img/social/forrst.png) no-repeat;} +.social-icons .github {background: url(../img/social/github.png) no-repeat;} +.social-icons .googleplus {background: url(../img/social/googleplus.png) no-repeat;} +.social-icons .jolicloud {background: url(../img/social/jolicloud.png) no-repeat;} +.social-icons .last-fm {background: url(../img/social/last-fm.png) no-repeat;} +.social-icons .linkedin {background: url(../img/social/linkedin.png) no-repeat;} +.social-icons .picasa {background: url(../img/social/picasa.png) no-repeat;} +.social-icons .pintrest {background: url(../img/social/pintrest.png) no-repeat;} +.social-icons .rss {background: url(../img/social/rss.png) no-repeat;} +.social-icons .skype {background: url(../img/social/skype.png) no-repeat;} +.social-icons .spotify {background: url(../img/social/spotify.png) no-repeat;} +.social-icons .stumbleupon {background: url(../img/social/stumbleupon.png) no-repeat;} +.social-icons .tumblr {background: url(../img/social/tumblr.png) no-repeat;} +.social-icons .twitter {background: url(../img/social/twitter.png) no-repeat;} +.social-icons .vimeo {background: url(../img/social/vimeo.png) no-repeat;} +.social-icons .wordpress {background: url(../img/social/wordpress.png) no-repeat;} +.social-icons .xing {background: url(../img/social/xing.png) no-repeat;} +.social-icons .yahoo {background: url(../img/social/yahoo.png) no-repeat;} +.social-icons .youtube {background: url(../img/social/youtube.png) no-repeat;} +.social-icons .vk {background: url(../img/social/vk.png) no-repeat;} +.social-icons .instagram {background: url(../img/social/instagram.png) no-repeat;} +.social-icons .reddit {background: url(../img/social/reddit.png) no-repeat;} +.social-icons .aboutme {background: url(../img/social/aboutme.png) no-repeat;} +.social-icons .flickr {background: url(../img/social/flickr.png) no-repeat;} +.social-icons .foursquare {background: url(../img/social/foursquare.png) no-repeat;} +.social-icons .gravatar {background: url(../img/social/gravatar.png) no-repeat;} +.social-icons .klout {background: url(../img/social/klout.png) no-repeat;} +.social-icons .myspace {background: url(../img/social/myspace.png) no-repeat;} +.social-icons .quora {background: url(../img/social/quora.png) no-repeat;} + +/*** +Inline Social Icons +***/ + +.social-icon { + display:inline-block !important; + width:28px; + height:28px; + background-position:0 0; + background-repeat:no-repeat; + transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -webkit-transition: all 0.3s ease-in-out; +} + +.social-icon.amazon {background: url(../img/social/amazon.png) no-repeat;} +.social-icon.behance {background: url(../img/social/behance.png) no-repeat;} +.social-icon.blogger {background: url(../img/social/blogger.png) no-repeat;} +.social-icon.deviantart {background: url(../img/social/deviantart.png) no-repeat;} +.social-icon.dribbble {background: url(../img/social/dribbble.png) no-repeat;} +.social-icon.dropbox {background: url(../img/social/dropbox.png) no-repeat;} +.social-icon.evernote {background: url(../img/social/evernote.png) no-repeat;} +.social-icon.facebook {background: url(../img/social/facebook.png) no-repeat;} +.social-icon.forrst {background: url(../img/social/forrst.png) no-repeat;} +.social-icon.github {background: url(../img/social/github.png) no-repeat;} +.social-icon.googleplus {background: url(../img/social/googleplus.png) no-repeat;} +.social-icon.jolicloud {background: url(../img/social/jolicloud.png) no-repeat;} +.social-icon.last-fm {background: url(../img/social/last-fm.png) no-repeat;} +.social-icon.linkedin {background: url(../img/social/linkedin.png) no-repeat;} +.social-icon.picasa {background: url(../img/social/picasa.png) no-repeat;} +.social-icon.pintrest {background: url(../img/social/pintrest.png) no-repeat;} +.social-icon.rss {background: url(../img/social/rss.png) no-repeat;} +.social-icon.skype {background: url(../img/social/skype.png) no-repeat;} +.social-icon.spotify {background: url(../img/social/spotify.png) no-repeat;} +.social-icon.stumbleupon {background: url(../img/social/stumbleupon.png) no-repeat;} +.social-icon.tumblr {background: url(../img/social/tumblr.png) no-repeat;} +.social-icon.twitter {background: url(../img/social/twitter.png) no-repeat;} +.social-icon.vimeo {background: url(../img/social/vimeo.png) no-repeat;} +.social-icon.wordpress {background: url(../img/social/wordpress.png) no-repeat;} +.social-icon.xing {background: url(../img/social/xing.png) no-repeat;} +.social-icon.yahoo {background: url(../img/social/yahoo.png) no-repeat;} +.social-icon.youtube {background: url(../img/social/youtube.png) no-repeat;} +.social-icon.vk {background: url(../img/social/vk.png) no-repeat;} +.social-icon.instagram {background: url(../img/social/instagram.png) no-repeat;} +.social-icon.reddit {background: url(../img/social/reddit.png) no-repeat;} +.social-icon.aboutme {background: url(../img/social/aboutme.png) no-repeat;} +.social-icon.flickr {background: url(../img/social/flickr.png) no-repeat;} +.social-icon.foursquare {background: url(../img/social/foursquare.png) no-repeat;} +.social-icon.gravatar {background: url(../img/social/gravatar.png) no-repeat;} +.social-icon.klout {background: url(../img/social/klout.png) no-repeat;} +.social-icon.myspace {background: url(../img/social/myspace.png) no-repeat;} +.social-icon.quora {background: url(../img/social/quora.png) no-repeat;} + +.social-icon:hover { + background-position:0 -38px; +} + +.social-icon-color { + opacity: 0.7; + background-position:0 -38px !important; +} + +.social-icon-color:hover { + opacity: 1; +} + +/*** +Notes +***/ + +/* Common styles for all types */ +.note { + margin: 0 0 20px 0; + padding: 15px 30px 15px 15px; + border-left: 5px solid #eee; +} + +.note h1, +.note h2, +.note h3, +.note h4 { + margin-top: 0; +} + +.note p:last-child { + margin-bottom: 0; +} +.note code, +.note .highlight { + background-color: #fff; +} + +/* Variations */ +.note-danger { + background-color: #FAEAE6; + border-color: #ed4e2a; +} + +.note-warning { + background-color: #FCF3E1; + border-color: #fcb322; +} + +.note-info { + background-color: #E8F6FC; + border-color: #57b5e3; +} + +.note-success { + background-color: #EBFCEE; + border-color: #3cc051; +} + +/*** +Demo Utils +***/ +.scrollspy-example { + position: relative; + height: 200px; + margin-top: 10px; + overflow: auto; +} + +.util-btn-margin-bottom-5 .btn { + margin-bottom: 5px !important; +} + +.util-btn-group-margin-bottom-5 .btn-group { + margin-bottom: 5px !important; +} + +.fontawesome-demo i { + font-size: 18px; +} + +.fontawesome-demo li { + padding-top: 5px; + padding-bottom: 5px; +} + +.glyphicons-demo ul { + padding-left: 0; + padding-bottom: 1px; + margin-bottom: 20px; + list-style: none; + overflow: hidden; +} + +.bs-glyphicons { + padding-left: 0; + padding-bottom: 1px; + margin-bottom: 20px; + list-style: none; + overflow: hidden; +} +.glyphicons-demo ul li { + float: left; + width: 25%; + height: 115px; + padding: 10px; + margin: 0 -1px -1px 0; + font-size: 12px; + line-height: 1.4; + text-align: center; + border: 1px solid #ddd; +} + +.glyphicons-demo .glyphicon { + display: block; + margin: 5px auto 10px; + font-size: 24px; +} +.glyphicons-demo ul li:hover { + background-color: rgba(86,61,124,.1); +} + +@media (min-width: 768px) { + .glyphicons-demo ul li { + width: 12.5%; + } +} + +/*** +Forms +****/ + +.static-info { + margin-bottom: 10px; +} + +.static-info .name { + font-size: 14px; +} + +.static-info .value { + font-size: 14px; + font-weight: 600; +} + +.static-info.align-reverse .name, +.static-info.align-reverse .value { + text-align: right; +} + +input.placeholder, +textarea.placeholder { + color: #aaa !important; +} + +.help-block { + margin-top: 5px; + margin-bottom: 5px; +} + +.form-inline input { + margin-bottom: 0px !important; +} + +.control-label { + margin-top: 2px; +} + +.form-control-static { + font-size: 14px; + padding-top: 7px; +} + +.control-label .required { + color: #e02222; + font-size: 12px; + padding-left: 2px; +} + +.switch-wrapper { + display: inline-block; +} + +.form { + padding: 0 !important; +} + +.form-body { + padding: 10px; +} + +.form-actions { + padding: 20px 10px; + margin-top: 20px; + background-color: #f5f5f5; + border-top: 1px solid #e5e5e5; + *zoom: 1; +} + +.form-actions.nobg { + background-color: transparent; +} + +.form-actions.top { + margin-top: 0; + margin-bottom: 20px; + border-top: 0; + border-bottom: 1px solid #e5e5e5; +} + +.form-actions.fluid { + padding: 20px 0; +} + +.form-actions.fluid > [class^="col-"] { + padding-left: 13px; +} + +.form-actions:before, +.form-actions:after { + display: table; + line-height: 0; + content: ""; +} + +.form-actions:after { + clear: both; +} + +.form-section { + margin: 30px 0px 25px 0px; + padding-bottom: 5px; + border-bottom: 1px solid #eee; +} + +.form .form-section:first-child { + margin-top: 5px; +} + +.help-inline { + font-size: 13px; + color: #737373; + display: inline-block; + padding: 5px; +} + +/* left, right aligned form actions */ +.form-actions.right { + padding-left: 0; + padding-right: 10px; + text-align: right; +} + +.form-actions.left { + padding-left: 10px; + padding-right: 0; + text-align: left; +} + +/* Checkboxes */ +.form-group .checkbox { + padding-left: 0; +} + +.checkbox-list > label { + display: block; +} + +.checkbox-list > label.checkbox-inline { + display: inline-block; +} + +.checkbox-list > label.checkbox-inline:first-child { + padding-left: 0; +} + +/* Radios */ + +.radio-list > label { + display: block; +} + +.radio-list > label.radio-inline { + display: inline-block; +} + +.radio-list > label.radio-inline:first-child { + padding-left: 0; +} + +.form-horizontal .radio-list .radio { + padding-top: 1px; +} + +.form-horizontal .radio-list > label { + margin-bottom: 0; +} + +.form-horizontal .radio > span { + margin-top: 2px; +} + +/* Rows seperated form layout */ +.form-row-seperated .form-group { + margin: 0; + border-bottom: 1px solid #efefef; + padding: 10px 0px 10px 0px; +} + +.form-row-seperated .form-group.last { + border-bottom: 0; + margin-bottom: 0; + padding-bottom: 10px; +} + +.form-row-seperated .form-actions { + margin-top: 0; +} + +.form-row-seperated .form-body { + padding: 0; + margin-top: 0; +} + +.form-row-seperated .help-block { + margin-bottom: 0; +} + +/* form bordered */ +.form-bordered .form-body { + margin: 0; + padding: 0; +} + +.form-bordered .form-actions { + margin-top: 0; +} + +.form-bordered .form-group { + margin: 0; + border-bottom: 1px solid #efefef; +} + +.form-bordered .form-group.last { + border-bottom: 0; +} + +.form-bordered .help-block { + margin-bottom: 0; +} + +.form-bordered .control-label { + padding-top: 16px; +} + +.form-bordered .form-group > div { + padding: 10px; + border-left: 1px solid #efefef; +} + +.form-bordered .form-actions.fluid > .row > div { + padding-left: 10px; +} + +.form-horizontal.form-bordered.form-row-stripped .form-group:nth-child(even) { + background-color: #fcfcfc; +} + +.form-horizontal.form-bordered.form-label-stripped .form-group:nth-child(even) { + background-color: #fcfcfc; +} + +.form-horizontal.form-bordered.form-row-stripped .form-control { + background: #fff !important; +} + +.form-horizontal.form-bordered.form-label-stripped .form-group:nth-child(even) > div { + background-color: #ffffff; +} + +/*** +Bordered form layout +***/ + +.form-bordered .form-control { + margin: 0; +} + + +/*** +Disabled Menu Link +***/ + +.disabled-link > a > span.text, +.disabled-link > a > span.title { + font-style: italic !important; + color: #888 !important; +} + +.disabled-link > a:hover { + cursor: not-allowed !important; +} + + +/*** +Responsive & Scrollable Tables +***/ + +.table-scrollable { + width: 100%; + overflow-x: auto; + overflow-y: hidden; + border: 1px solid #dddddd; + margin: 10px 0 !important; +} + +.table-scrollable > .table { + width: 100% !important; + margin: 0 !important; + margin-bottom: 0; + background-color: #fff; +} + +.table-scrollable > .table > thead > tr > th, +.table-scrollable > .table > tbody > tr > th, +.table-scrollable > .table > tfoot > tr > th, +.table-scrollable > .table > thead > tr > td, +.table-scrollable > .table > tbody > tr > td, +.table-scrollable > .table > tfoot > tr > td { + white-space: nowrap; +} + +.table-scrollable > .table-bordered { + border: 0; +} + +.table-scrollable > .table-bordered > thead > tr > th:first-child, +.table-scrollable > .table-bordered > tbody > tr > th:first-child, +.table-scrollable > .table-bordered > tfoot > tr > th:first-child, +.table-scrollable > .table-bordered > thead > tr > td:first-child, +.table-scrollable > .table-bordered > tbody > tr > td:first-child, +.table-scrollable > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} + +.table-scrollable > .table-bordered > thead > tr > th:last-child, +.table-scrollable > .table-bordered > tbody > tr > th:last-child, +.table-scrollable > .table-bordered > tfoot > tr > th:last-child, +.table-scrollable > .table-bordered > thead > tr > td:last-child, +.table-scrollable > .table-bordered > tbody > tr > td:last-child, +.table-scrollable > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} + +.table-scrollable > .table-bordered > thead > tr:last-child > th, +.table-scrollable > .table-bordered > tbody > tr:last-child > th, +.table-scrollable > .table-bordered > tfoot > tr:last-child > th, +.table-scrollable > .table-bordered > thead > tr:last-child > td, +.table-scrollable > .table-bordered > tbody > tr:last-child > td, +.table-scrollable > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; +} + +/*** +Responsive Flip Scroll Tables +***/ + +.flip-scroll table { width: 100%; } + +@media only screen and (max-width: 768px) { + + .flip-scroll .flip-content:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } + .flip-scroll * html .flip-content { zoom: 1; } + .flip-scroll *:first-child+html .flip-content { zoom: 1; } + + .flip-scroll table { width: 100%; border-collapse: collapse; border-spacing: 0; } + + .flip-scroll th, + .flip-scroll td { margin: 0; vertical-align: top; } + .flip-scroll th { + text-align: left; + border: 0 !important; + border-bottom: 1px solid #ddd !important; + border-right: 1px solid #ddd !important; + font-size: 13px !important; + padding: 5px; + width: auto !important; + } + + .flip-scroll table { display: block; position: relative; width: 100%; } + .flip-scroll thead { + display: block; + float: left; + } + .flip-scroll tbody { + display: block; + width: auto; + position: relative; + overflow-x: auto; + white-space: nowrap; + } + .flip-scroll thead tr { display: block; } + .flip-scroll th { display: block; text-align: right; } + .flip-scroll tbody tr { display: inline-block; vertical-align: top; margin-left: -5px; } + .flip-scroll td { display: block; min-height: 1.25em; text-align: left; border-top: 0 !important; border-left: 0 !important; border-right: 0 !important} + + /* sort out borders */ + + .flip-scroll th { border-bottom: 0; border-left: 0; } + .flip-scroll td { border-left: 0; border-right: 0; border-bottom: 0; } + .flip-scroll tbody tr { border-left: 1px solid #ddd; } + .flip-scroll th:last-child, + .flip-scroll td:last-child { border-bottom: 1px solid #ddd; } + +} + +/*** +UI Loading +***/ + +.loading-message { + display: inline-block; + min-width: 125px; + padding: 10px; + margin: 0 auto; + color: #000 !important; + font-size: 13px; + font-weight: 400; + text-align: center; + vertical-align: middle; +} + +.loading-message span { + line-height:20px; + vertical-align: middle; +} + +.loading-message.loading-message-boxed { + border: 1px solid #ddd; + background-color: #eee; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); +} + +.page-loading { + position: fixed; + top: 50%; + left: 50%; + min-width: 125px; + margin-left: -50px; + margin-top: -30px; + padding: 7px; + text-align: center; + color: #333; + font-size: 13px; + border: 1px solid #ddd; + background-color: #eee; + vertical-align: middle; + -webkit-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + -moz-box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); +} + +.page-loading span { + line-height:20px; + vertical-align: middle; +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/css/themes/blue.css b/cas-server-webapp/src/main/webapp/assets/css/themes/blue.css new file mode 100644 index 0000000..0895a20 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/themes/blue.css @@ -0,0 +1,356 @@ +/*** +Blue theme +***/ +/*** +Reset and overrides +***/ +body { + background-color: #1570a6 !important; +} +/*** +Page header +***/ +.header { + filter: none !important; + background-image: none !important; + background-color: #0f4e74 !important; +} +.header .btn-navbar { + background-color: #0f4e74 !important; +} +.header .navbar-nav .dropdown-toggle:hover, +.header .navbar-nav .dropdown.open .dropdown-toggle { + color: #fff; + background-color: #146a9d !important; +} +.header .navbar-nav li.dropdown .dropdown-toggle i { + color: #68bbec !important; +} +/*** +Header Search +***/ +.header .search-form { + background-color: #0B4263; +} + +.header .search-form .form-control{ + color: #68bbec; + border: 0; + background-color: #0B4263; +} + +.header .search-form .submit { + background: url(../../img/search-icon-blue.png); +} + +/*** +Hor menu +***/ +.header .hor-menu ul.nav li a { + color: #ccc; +} + +.header .hor-menu ul.nav li.open > a, +.header .hor-menu ul.nav li > a:hover, +.header .hor-menu ul.nav li > a:focus { + color: #fff; + background: #146a9d; +} + +.header .hor-menu .dropdown-menu li:hover > a, +.header .hor-menu ul.nav li.active > a, +.header .hor-menu ul.nav li.active > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu ul.nav li.current > a, +.header .hor-menu ul.nav li.current > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu .dropdown-menu { + background: #146a9d; +} +.header .hor-menu .dropdown-menu li > a { + color: #ccc; +} + +.header .hor-menu .hor-menu-search-form-toggler.off { + background: #146a9d url(../../img/hor-menu-search-close-white.png) no-repeat center; +} + +.header .hor-menu .search-form { + background:#146a9d; +} + +.header .hor-menu .search-form form input { + color: #ccc; +} + +.header .hor-menu .search-form .btn { + color: #ccc; + background: url(../../img/search-icon-white.png) no-repeat center; +} + +.header .hor-menu .search-form form input::-webkit-input-placeholder { /* WebKit browsers */ + color: #ccc; +} +.header .hor-menu .search-form form input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #ccc; +} +.header .hor-menu .search-form form input::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #ccc; +} +.header .hor-menu .search-form form input:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #ccc; +} + +/*** +Mega Menu +***/ +.mega-menu .mega-menu-submenu { + border-right: 1px solid #1876AD; +} + +.mega-menu .mega-menu-submenu li h3 { + color: #fff; +} + +/*** +Page sidebar +***/ +.page-sidebar { + background-color: #1570a6; +} +ul.page-sidebar-menu > li > a { + border-top: 1px solid #1c95dc !important; + color: #ffffff !important; +} +ul.page-sidebar-menu > li:last-child > a { + border-bottom: 1px solid transparent !important; +} +ul.page-sidebar-menu > li a i { + color: #7fc5ef; +} +ul.page-sidebar-menu > li.open > a, +ul.page-sidebar-menu > li > a:hover, +ul.page-sidebar-menu > li:hover > a { + background: #12618f; +} +ul.page-sidebar-menu > li.active > a { + background: #cc1d1d !important; + border-top-color: transparent !important; + color: #ffffff; +} +ul.page-sidebar-menu > li.active > a i { + color: #ffffff; +} +ul.page-sidebar-menu > li > ul.sub-menu > li:first-child > a { + border-top: 0px !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + color: #ffffff !important; + background: #1b8fd3 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + background: #1b8fd3 !important; +} +/* 3rd level sub menu */ +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a:hover, +ul.page-sidebar-menu > li > ul.sub-menu li.open > a { + color: #ffffff !important; + background: #1b8fd3 !important; +} +/* font color for all sub menu links*/ +ul.page-sidebar-menu li > ul.sub-menu > li > a { + color: #c3e4f7; +} +/* menu arrows */ +ul.page-sidebar-menu > li > a .arrow:before, +ul.page-sidebar-menu > li > a .arrow.open:before { + color: #51b1e9 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu a .arrow:before, +ul.page-sidebar-menu > li > ul.sub-menu a .arrow.open:before { + color: #3ba6e6 !important; +} +ul.page-sidebar-menu > li > a > .arrow.open:before { + color: #68bbec !important; +} +ul.page-sidebar-menu > li.active > a .arrow:before, +ul.page-sidebar-menu > li.active > a .arrow.open:before { + color: #ffffff !important; +} +/* sidebar search */ +.page-sidebar .sidebar-search input { + background-color: #0f5179 !important; + color: #51b1e9; +} +.page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #51b1e9 !important; +} +.page-sidebar .sidebar-search input:-moz-placeholder { + color: #51b1e9 !important; +} +.page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #51b1e9 !important; +} +.page-sidebar .sidebar-search input { + background-color: #1570a6 !important; + color: #bfbfbf !important; +} +.page-sidebar .sidebar-search .input-box { + border-bottom: 1px solid #51b1e9 !important; +} +.page-sidebar .sidebar-search .submit { + background-image: url(../../img/search-icon-blue.png); +} +/*** +Sidebar toggler +***/ +.sidebar-toggler { + background-image: url(../../img/sidebar-toggler-blue.jpg); + background-color: #0f5179; +} +/* search box bg color on expanded */ +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + background-color: #1570a6 !important; +} +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-image: url("../../img/sidebar-search-close-blue.png"); +} +/* sub menu bg color on hover menu item */ +.page-sidebar-closed ul.page-sidebar-menu > li:hover .sub-menu { + background-color: #1570a6; +} +/*** +Footer +***/ +.footer .footer-inner { + color: #68bbec; +} +.footer .footer-tools .go-top { + background-color: #1985c6; +} +.footer .footer-tools .go-top:hover { + opacity: 0.7; + filter: alpha(opacity=70); +} +.footer .footer-tools .go-top i { + color: #68bbec; +} +/*** +Footer Layouts (new in v1.3) +***/ +/* begin:fixed footer */ +.page-footer-fixed .footer { + background-color: #0f5179; +} +.page-footer-fixed .footer .footer-inner { + color: #68bbec; +} +.page-footer-fixed .footer .footer-tools .go-top { + background-color: #1985c6; +} +.page-footer-fixed .footer .footer-tools .go-top i { + color: #68bbec; +} +/* end:fixed footer */ +/*** +Gritter Notifications +***/ +.gritter-top { + background: url(../../plugins/gritter/images/gritter-blue.png) no-repeat left -30px !important; +} +.gritter-bottom { + background: url(../../plugins/gritter/images/gritter-blue.png) no-repeat left bottom !important; +} +.gritter-item { + display: block; + background: url(../../plugins/gritter/images/gritter-blue.png) no-repeat left -40px !important; +} +.gritter-close { + background: url(../../plugins/gritter/images/gritter-blue.png) no-repeat left top !important; +} +.gritter-title { + text-shadow: none !important; + /* Not supported by IE :( */ + +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-light .gritter-close { + background-image: url(../../plugins/gritter/images/gritter-light.png) !important; +} +.gritter-item-wrapper a { + color: #18a5ed; +} +.gritter-item-wrapper a:hover { + color: #0b6694; +} +/* begin: boxed page */ +@media (min-width: 992px) { + .page-boxed { + background-color: #125e8b !important; + } + .page-boxed .page-container { + background-color: #1570a6; + border-left: 1px solid #1c98e1; + border-bottom: 1px solid #1c98e1; + } + .page-boxed.page-sidebar-reversed .page-container { + border-left: 0; + border-right: 1px solid #1c98e1; + } + .page-boxed.page-sidebar-fixed .page-container { + border-left: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-container { + border-left: 0; + border-right: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-fixed .page-sidebar { + border-left: 1px solid #1c98e1; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #1c98e1; + border-left: 0; + } + .page-boxed.page-sidebar-fixed.page-footer-fixed .footer { + background-color: #125e8b !important; + } +} +/* end: boxed page */ +/*** +Landscape phone to portrait tablet +***/ +@media (max-width: 991px) { + /*** + page sidebar + ***/ + .page-sidebar { + background-color: #105882 !important; + } + ul.page-sidebar-menu > li > a { + border-top: 1px solid #187fbd !important; + } + ul.page-sidebar-menu > li:last-child > a { + border-bottom: 0 !important; + } + .page-sidebar .sidebar-search input { + background-color: #105882 !important; + } + ul.page-sidebar-menu > li.open > a, + ul.page-sidebar-menu > li > a:hover, + ul.page-sidebar-menu > li:hover > a { + background: #0e4b70; + } +} diff --git a/cas-server-webapp/src/main/webapp/assets/css/themes/brown.css b/cas-server-webapp/src/main/webapp/assets/css/themes/brown.css new file mode 100644 index 0000000..ddb76c8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/themes/brown.css @@ -0,0 +1,355 @@ +/*** +Brown theme +***/ +/*** +Reset and overrides +***/ +body { + background-color: #623f18 !important; +} +/*** +Page header +***/ +.header { + filter: none !important; + background-image: none !important; + background-color: #35220d !important; +} +.header .btn-navbar { + background-color: #35220d !important; +} +.header .navbar-nav .dropdown-toggle:hover, +.header .navbar-nav .dropdown.open .dropdown-toggle { + background-color: #5a3a16 !important; +} +.header .navbar-nav li.dropdown .dropdown-toggle i { + color: #d18d42 !important; +} +/*** +Header Search +***/ +.header .search-form { + background-color: #241709; +} + +.header .search-form .form-control{ + color: #ccc; + border: 0; + background-color: #241709; +} + +.header .search-form .submit { + background: url(../../img/search-icon-brown.png); +} +/*** +Hor menu +***/ +.header .hor-menu ul.nav li a { + color: #ccc; +} + +.header .hor-menu ul.nav li.open > a, +.header .hor-menu ul.nav li > a:hover, +.header .hor-menu ul.nav li > a:focus { + color: #fff; + background: #5a3a16; +} + +.header .hor-menu .dropdown-menu li:hover > a, +.header .hor-menu ul.nav li.active > a, +.header .hor-menu ul.nav li.active > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu ul.nav li.current > a, +.header .hor-menu ul.nav li.current > a:hover { + color: #fff; + background: #e02222 !important; +} + + +.header .hor-menu .dropdown-menu { + background: #5a3a16; +} +.header .hor-menu .dropdown-menu li > a { + color: #ccc; +} + +.header .hor-menu .hor-menu-search-form-toggler.off { + background: #5a3a16 url(../../img/hor-menu-search-close-white.png) no-repeat center; +} + +.header .hor-menu .search-form { + background:#5a3a16; +} + +.header .hor-menu .search-form form input { + color: #ccc; +} + +.header .hor-menu .search-form .btn { + color: #ccc; + background: url(../../img/search-icon-white.png) no-repeat center; +} + +.header .hor-menu .search-form form input::-webkit-input-placeholder { /* WebKit browsers */ + color: #ccc; +} +.header .hor-menu .search-form form input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #ccc; +} +.header .hor-menu .search-form form input::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #ccc; +} +.header .hor-menu .search-form form input:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #ccc; +} + +/*** +Mega Menu +***/ +.mega-menu .mega-menu-submenu { + border-right: 1px solid #6B451B; +} + +.mega-menu .mega-menu-submenu li h3 { + color: #fff; +} + +/*** +Page sidebar +***/ +.page-sidebar { + background-color: #623f18; +} +ul.page-sidebar-menu > li > a { + border-top: 1px solid #935f24 !important; + color: #ffffff !important; +} +ul.page-sidebar-menu > li:last-child > a { + border-bottom: 1px solid transparent !important; +} +ul.page-sidebar-menu > li a i { + color: #9a6d3a; +} +ul.page-sidebar-menu > li.open > a, +ul.page-sidebar-menu > li > a:hover, +ul.page-sidebar-menu > li:hover > a { + background: #4e3112; +} +ul.page-sidebar-menu > li.active > a { + background: #4e3112 !important; + border-top-color: transparent !important; + color: #ffffff; +} +ul.page-sidebar-menu > li.active > a i { + color: #ffffff; +} +ul.page-sidebar-menu > li > ul.sub-menu > li:first-child > a { + border-top: 0px !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + color: #ffffff !important; + background: #8b5922 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + background: #8b5922 !important; +} +/* 3rd level sub menu */ +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a:hover, +ul.page-sidebar-menu > li > ul.sub-menu li.open > a { + color: #ffffff !important; + background: #8b5922 !important; +} +/* font color for all sub menu links*/ +ul.page-sidebar-menu li > ul.sub-menu > li > a { + color: #e5bf94; +} +/* menu arrows */ +ul.page-sidebar-menu > li > a .arrow:before, +ul.page-sidebar-menu > li > a .arrow.open:before { + color: #c88131 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu a .arrow:before, +ul.page-sidebar-menu > li > ul.sub-menu a .arrow.open:before { + color: #b4742c !important; +} +ul.page-sidebar-menu > li > a > .arrow.open:before { + color: #d18d42 !important; +} +ul.page-sidebar-menu > li.active > a .arrow:before, +ul.page-sidebar-menu > li.active > a .arrow.open:before { + color: #ffffff !important; +} +/* sidebar search */ +.page-sidebar .sidebar-search input { + background-color: #39250e !important; + color: #b18d65; +} +.page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #b18d65 !important; +} +.page-sidebar .sidebar-search input:-moz-placeholder { + color: #b18d65 !important; +} +.page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #b18d65 !important; +} +.page-sidebar .sidebar-search input { + background-color: #623f18 !important; + color: #b18d65 !important; +} +.page-sidebar .sidebar-search .input-box { + border-bottom: 1px solid #845f36 !important; +} +.page-sidebar .sidebar-search .submit { + background-image: url(../../img/search-icon-brown.png); +} +/*** +Sidebar toggler +***/ +.sidebar-toggler { + background-image: url(../../img/sidebar-toggler-brown.jpg); + background-color: #39250e; +} +/* search box bg color on expanded */ +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + background-color: #623f18 !important; +} +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-image: url("../../img/sidebar-search-close-brown.png"); +} +/* sub menu bg color on hover menu item */ +.page-sidebar-closed ul.page-sidebar-menu > li:hover .sub-menu { + background-color: #623f18; +} +/*** +Footer +***/ +.footer .footer-inner { + color: #999999; +} +.footer .footer-tools .go-top { + background-color: #7f511f; +} +.footer .footer-tools .go-top:hover { + opacity: 0.7; + filter: alpha(opacity=70); +} +.footer .footer-tools .go-top i { + color: #d18d42; +} +/*** +Footer Layouts (new in v1.3) +***/ +/* begin:fixed footer */ +.page-footer-fixed .footer { + background-color: #39250e; +} +.page-footer-fixed .footer .footer-inner { + color: #999999; +} +.page-footer-fixed .footer .footer-tools .go-top { + background-color: #7f511f; +} +.page-footer-fixed .footer .footer-tools .go-top i { + color: #d18d42; +} +/* end:fixed footer */ +/*** +Gritter Notifications +***/ +.gritter-top { + background: url(../../plugins/gritter/images/gritter-brown.png) no-repeat left -30px !important; +} +.gritter-bottom { + background: url(../../plugins/gritter/images/gritter-brown.png) no-repeat left bottom !important; +} +.gritter-item { + display: block; + background: url(../../plugins/gritter/images/gritter-brown.png) no-repeat left -40px !important; +} +.gritter-close { + background: url(../../plugins/gritter/images/gritter-brown.png) no-repeat left top !important; +} +.gritter-title { + text-shadow: none !important; + /* Not supported by IE :( */ + +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-light .gritter-close { + background-image: url(../../plugins/gritter/images/gritter-light.png) !important; +} +.gritter-item-wrapper a { + color: #b18d65; +} +.gritter-item-wrapper a:hover { + color: #755a3b; +} +/* begin: boxed page */ +@media (min-width: 992px) { + .page-boxed { + background-color: #492f12 !important; + } + .page-boxed .page-container { + background-color: #623f18; + border-left: 1px solid #976125; + border-bottom: 1px solid #976125; + } + .page-boxed.page-sidebar-reversed .page-container { + border-left: 0; + border-right: 1px solid #976125; + } + .page-boxed.page-sidebar-fixed .page-container { + border-left: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-container { + border-left: 0; + border-right: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-fixed .page-sidebar { + border-left: 1px solid #976125; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #976125; + border-left: 0; + } + .page-boxed.page-sidebar-fixed.page-footer-fixed .footer { + background-color: #492f12 !important; + } +} +/* end: boxed page */ +/*** +Landscape phone to portrait tablet +***/ +@media (max-width: 991px) { + /*** + page sidebar + ***/ + .page-sidebar { + background-color: #412a10 !important; + } + ul.page-sidebar-menu > li > a { + border-top: 1px solid #764c1d !important; + } + ul.page-sidebar-menu > li:last-child > a { + border-bottom: 0 !important; + } + .page-sidebar .sidebar-search input { + background-color: #412a10 !important; + } + ul.page-sidebar-menu > li.open > a, + ul.page-sidebar-menu > li > a:hover, + ul.page-sidebar-menu > li:hover > a { + background: #311f0c; + } +} diff --git a/cas-server-webapp/src/main/webapp/assets/css/themes/default.css b/cas-server-webapp/src/main/webapp/assets/css/themes/default.css new file mode 100644 index 0000000..8c68bc0 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/themes/default.css @@ -0,0 +1,370 @@ +/*** +Default theme +***/ + +/*** +Reset and overrides +***/ +body { + background-color: #3d3d3d !important; +} +/*** +Page header +***/ +.header { + filter: none !important; + background-image: none !important; + background-color: #212121 !important; +} +.header .btn-navbar { + background-color: #212121 !important; +} +.header .navbar-nav .dropdown-toggle:hover, +.header .navbar-nav .dropdown.open .dropdown-toggle { + background-color: #383838 !important; +} +.header .navbar-nav li.dropdown .dropdown-toggle i { + color: #8a8a8a !important; +} + +/*** +Header Search +***/ +.header .search-form { + background-color: #000; +} + +.header .search-form .form-control { + color: #999; + border: 0; + background-color: #000; +} + +.header .search-form .form-control::-webkit-input-placeholder { /* WebKit browsers */ + color: #777; +} +.header .search-form .form-control:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #777; +} +.header .search-form .form-control::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #777; +} +.header .search-form .form-control:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #777; +} + +.header .search-form .submit { + background: url(../../img/search-icon-light.png); +} + +/*** +Hor menu +***/ +.header .hor-menu ul.nav li a { + color: #999; +} + +.header .hor-menu ul.nav li.open > a, +.header .hor-menu ul.nav li > a:hover, +.header .hor-menu ul.nav li > a:focus { + color: #fff; + background: #383838; +} + +.header .hor-menu .dropdown-menu li:hover > a, +.header .hor-menu ul.nav li.active > a, +.header .hor-menu ul.nav li.active > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu ul.nav li.current > a, +.header .hor-menu ul.nav li.current > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu .dropdown-menu { + background: #383838; +} +.header .hor-menu .dropdown-menu li > a { + color: #999; +} + +.header .hor-menu .hor-menu-search-form-toggler.off { + background: #383838 url(../../img/hor-menu-search-close.png) no-repeat center; +} + +.header .hor-menu .search-form { + background:#383838; +} + +.header .hor-menu .search-form form input { + color: #999; +} + +.header .hor-menu .search-form .btn { + color: #999; + background: url(../../img/search-icon.png) no-repeat center; +} + +.header .hor-menu .search-form form input::-webkit-input-placeholder { /* WebKit browsers */ + color: #999; +} +.header .hor-menu .search-form form input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #999; +} +.header .hor-menu .search-form form input::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #999; +} +.header .hor-menu .search-form form input:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #999; +} + +/*** +Mega Menu +***/ +.mega-menu .mega-menu-submenu { + border-right: 1px solid #444; +} + +.mega-menu .mega-menu-submenu li h3 { + color: #fff; +} + +/*** +Page sidebar +***/ +.page-sidebar { + background-color: #3d3d3d; +} +ul.page-sidebar-menu > li > a { + border-top: 1px solid #5c5c5c !important; + color: #ffffff !important; +} +ul.page-sidebar-menu > li:last-child > a { + border-bottom: 1px solid transparent !important; +} +ul.page-sidebar-menu > li a i { + color: #969696; +} +ul.page-sidebar-menu > li.open > a, +ul.page-sidebar-menu > li > a:hover, +ul.page-sidebar-menu > li:hover > a { + background: #303030; +} +ul.page-sidebar-menu > li.active > a { + background: #e02222 !important; + border-top-color: transparent !important; + color: #ffffff; +} +ul.page-sidebar-menu > li.active > a i { + color: #ffffff; +} +ul.page-sidebar-menu > li > ul.sub-menu > li:first-child > a { + border-top: 0px !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + color: #ffffff !important; + background: #575757 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + background: #575757 !important; +} +/* 3rd level sub menu */ +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a:hover, +ul.page-sidebar-menu > li > ul.sub-menu li.open > a { + color: #ffffff !important; + background: #575757 !important; +} +/* font color for all sub menu links*/ +ul.page-sidebar-menu li > ul.sub-menu > li > a { + color: #bdbdbd; +} +/* menu arrows */ +ul.page-sidebar-menu > li > a .arrow:before, +ul.page-sidebar-menu > li > a .arrow.open:before { + color: #7d7d7d !important; +} +ul.page-sidebar-menu > li > ul.sub-menu a .arrow:before, +ul.page-sidebar-menu > li > ul.sub-menu a .arrow.open:before { + color: #707070 !important; +} +ul.page-sidebar-menu > li > a > .arrow.open:before { + color: #8a8a8a !important; +} +ul.page-sidebar-menu > li.active > a .arrow:before, +ul.page-sidebar-menu > li.active > a .arrow.open:before { + color: #ffffff !important; +} +/* sidebar search */ +.page-sidebar .sidebar-search input { + background-color: #242424 !important; + color: #7d7d7d; +} +.page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #7d7d7d !important; +} +.page-sidebar .sidebar-search input:-moz-placeholder { + color: #7d7d7d !important; +} +.page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #7d7d7d !important; +} +.page-sidebar .sidebar-search input { + background-color: #3d3d3d !important; + color: #bfbfbf !important; +} +.page-sidebar .sidebar-search .input-box { + border-bottom: 1px solid #7d7d7d !important; +} +.page-sidebar .sidebar-search .submit { + background-image: url(../../img/search-icon.png); +} +/*** +Sidebar toggler +***/ +.sidebar-toggler { + background-image: url(../../img/sidebar-toggler.jpg); + background-color: #242424; +} +/* search box bg color on expanded */ +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + background-color: #3d3d3d !important; +} +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-image: url("../../img/sidebar-search-close.png"); +} +/* sub menu bg color on hover menu item */ +.page-sidebar-closed ul.page-sidebar-menu > li:hover .sub-menu { + background-color: #3d3d3d; +} +/*** +Footer +***/ +.footer .footer-inner { + color: #999999; +} +.footer .footer-tools .go-top { + background-color: #4f4f4f; +} +.footer .footer-tools .go-top:hover { + opacity: 0.7; + filter: alpha(opacity=70); +} +.footer .footer-tools .go-top i { + color: #8a8a8a; +} +/*** +Footer Layouts (new in v1.3) +***/ +/* begin:fixed footer */ +.page-footer-fixed .footer { + background-color: #242424; +} +.page-footer-fixed .footer .footer-inner { + color: #999999; +} +.page-footer-fixed .footer .footer-tools .go-top { + background-color: #4f4f4f; +} +.page-footer-fixed .footer .footer-tools .go-top i { + color: #8a8a8a; +} +/* end:fixed footer */ +/*** +Gritter Notifications +***/ +.gritter-top { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left -30px !important; +} +.gritter-bottom { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left bottom !important; +} +.gritter-item { + display: block; + background: url(../../plugins/gritter/images/gritter.png) no-repeat left -40px !important; +} +.gritter-close { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left top !important; +} +.gritter-title { + text-shadow: none !important; + /* Not supported by IE :( */ + +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-light .gritter-close { + background-image: url(../../plugins/gritter/images/gritter-light.png) !important; +} +.gritter-item-wrapper a { + color: #18a5ed; +} +.gritter-item-wrapper a:hover { + color: #0b6694; +} +/* begin: boxed page */ +@media (min-width: 992px) { + .page-boxed { + background-color: #2e2e2e !important; + } + .page-boxed .page-container { + background-color: #3d3d3d; + border-left: 1px solid #5e5e5e; + border-bottom: 1px solid #5e5e5e; + } + .page-boxed.page-sidebar-reversed .page-container { + border-left: 0; + border-right: 1px solid #5e5e5e; + } + .page-boxed.page-sidebar-fixed .page-container { + border-left: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-container { + border-left: 0; + border-right: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-fixed .page-sidebar { + border-left: 1px solid #5e5e5e; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #5e5e5e; + border-left: 0; + } + .page-boxed.page-sidebar-fixed.page-footer-fixed .footer { + background-color: #2e2e2e !important; + } +} +/* end: boxed page */ +/*** +Landscape phone to portrait tablet +***/ +@media (max-width: 991px) { + /*** + page sidebar + ***/ + .page-sidebar { + background-color: #292929 !important; + } + ul.page-sidebar-menu > li > a { + border-top: 1px solid #4a4a4a !important; + } + ul.page-sidebar-menu > li:last-child > a { + border-bottom: 0 !important; + } + .page-sidebar .sidebar-search input { + background-color: #292929 !important; + } + ul.page-sidebar-menu > li.open > a, + ul.page-sidebar-menu > li > a:hover, + ul.page-sidebar-menu > li:hover > a { + background: #1e1e1e; + } +} diff --git a/cas-server-webapp/src/main/webapp/assets/css/themes/grey.css b/cas-server-webapp/src/main/webapp/assets/css/themes/grey.css new file mode 100644 index 0000000..b8929f9 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/themes/grey.css @@ -0,0 +1,354 @@ +/*** +Grey theme +***/ +/*** +Reset and overrides +***/ +body { + background-color: #666666 !important; +} +/*** +Page header +***/ +.header { + filter: none !important; + background-image: none !important; + background-color: #4a4a4a !important; +} +.header .btn-navbar { + background-color: #4a4a4a !important; +} +.header .navbar-nav .dropdown-toggle:hover, +.header .navbar-nav .dropdown.open .dropdown-toggle { + background-color: #616161 !important; +} +.header .navbar-nav li.dropdown .dropdown-toggle i { + color: #b3b3b3 !important; +} +/*** +Header Search +***/ +.header .search-form { + background-color: #3a3a3a; +} + +.header .search-form .form-control{ + color: #ccc; + border: 0; + background-color: #3a3a3a; +} + +.header .search-form .submit { + background: url(../../img/search-icon-light.png); +} +/*** +Hor menu +***/ +.header .hor-menu ul.nav li a { + color: #ccc; +} + +.header .hor-menu ul.nav li.open > a, +.header .hor-menu ul.nav li > a:hover, +.header .hor-menu ul.nav li > a:focus { + color: #fff; + background: #616161; +} + +.header .hor-menu .dropdown-menu li:hover > a, +.header .hor-menu ul.nav li.active > a, +.header .hor-menu ul.nav li.active > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu ul.nav li.current > a, +.header .hor-menu ul.nav li.current > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu .dropdown-menu { + background: #616161; +} +.header .hor-menu .dropdown-menu li > a { + color: #ccc; +} + +.header .hor-menu .hor-menu-search-form-toggler.off { + background: #616161 url(../../img/hor-menu-search-close-white.png) no-repeat center; +} + +.header .hor-menu .search-form { + background:#616161; +} + +.header .hor-menu .search-form form input { + color: #ccc; +} + +.header .hor-menu .search-form .btn { + color: #ccc; + background: url(../../img/search-icon-white.png) no-repeat center; +} + +.header .hor-menu .search-form form input::-webkit-input-placeholder { /* WebKit browsers */ + color: #ccc; +} +.header .hor-menu .search-form form input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #ccc; +} +.header .hor-menu .search-form form input::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #ccc; +} +.header .hor-menu .search-form form input:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #ccc; +} + +/*** +Mega Menu +***/ +.mega-menu .mega-menu-submenu { + border-right: 1px solid #757575; +} + +.mega-menu .mega-menu-submenu li h3 { + color: #fff; +} + +/*** +Page sidebar +***/ +.page-sidebar { + background-color: #666666; +} +ul.page-sidebar-menu > li > a { + border-top: 1px solid #858585 !important; + color: #ffffff !important; +} +ul.page-sidebar-menu > li:last-child > a { + border-bottom: 1px solid transparent !important; +} +ul.page-sidebar-menu > li a i { + color: #bfbfbf; +} +ul.page-sidebar-menu > li.open > a, +ul.page-sidebar-menu > li > a:hover, +ul.page-sidebar-menu > li:hover > a { + background: #595959; +} +ul.page-sidebar-menu > li.active > a { + background: #e02222 !important; + border-top-color: transparent !important; + color: #ffffff; +} +ul.page-sidebar-menu > li.active > a i { + color: #ffffff; +} +ul.page-sidebar-menu > li > ul.sub-menu > li:first-child > a { + border-top: 0px !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + color: #ffffff !important; + background: #808080 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + background: #808080 !important; +} +/* 3rd level sub menu */ +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a:hover, +ul.page-sidebar-menu > li > ul.sub-menu li.open > a { + color: #ffffff !important; + background: #808080 !important; +} +/* font color for all sub menu links*/ +ul.page-sidebar-menu li > ul.sub-menu > li > a { + color: #e6e6e6; +} +/* menu arrows */ +ul.page-sidebar-menu > li > a .arrow:before, +ul.page-sidebar-menu > li > a .arrow.open:before { + color: #a6a6a6 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu a .arrow:before, +ul.page-sidebar-menu > li > ul.sub-menu a .arrow.open:before { + color: #999999 !important; +} +ul.page-sidebar-menu > li > a > .arrow.open:before { + color: #b3b3b3 !important; +} +ul.page-sidebar-menu > li.active > a .arrow:before, +ul.page-sidebar-menu > li.active > a .arrow.open:before { + color: #ffffff !important; +} +/* sidebar search */ +.page-sidebar .sidebar-search input { + background-color: #4d4d4d !important; + color: #a6a6a6; +} +.page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #a6a6a6 !important; +} +.page-sidebar .sidebar-search input:-moz-placeholder { + color: #a6a6a6 !important; +} +.page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #a6a6a6 !important; +} +.page-sidebar .sidebar-search input { + background-color: #666666 !important; + color: #bfbfbf !important; +} +.page-sidebar .sidebar-search .input-box { + border-bottom: 1px solid #a6a6a6 !important; +} +.page-sidebar .sidebar-search .submit { + background-image: url(../../img/search-icon.png); +} +/*** +Sidebar toggler +***/ +.sidebar-toggler { + background-image: url(../../img/sidebar-toggler.jpg); + background-color: #4d4d4d; +} +/* search box bg color on expanded */ +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + background-color: #666666 !important; +} +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-image: url("../../img/sidebar-search-close.png"); +} +/* sub menu bg color on hover menu item */ +.page-sidebar-closed ul.page-sidebar-menu > li:hover .sub-menu { + background-color: #666666; +} +/*** +Footer +***/ +.footer .footer-inner { + color: #b3b3b3; +} +.footer .footer-tools .go-top { + background-color: #787878; +} +.footer .footer-tools .go-top:hover { + opacity: 0.7; + filter: alpha(opacity=70); +} +.footer .footer-tools .go-top i { + color: #b3b3b3; +} +/*** +Footer Layouts (new in v1.3) +***/ +/* begin:fixed footer */ +.page-footer-fixed .footer { + background-color: #4d4d4d; +} +.page-footer-fixed .footer .footer-inner { + color: #b3b3b3; +} +.page-footer-fixed .footer .footer-tools .go-top { + background-color: #787878; +} +.page-footer-fixed .footer .footer-tools .go-top i { + color: #b3b3b3; +} +/* end:fixed footer */ +/*** +Gritter Notifications +***/ +.gritter-top { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left -30px !important; +} +.gritter-bottom { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left bottom !important; +} +.gritter-item { + display: block; + background: url(../../plugins/gritter/images/gritter.png) no-repeat left -40px !important; +} +.gritter-close { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left top !important; +} +.gritter-title { + text-shadow: none !important; + /* Not supported by IE :( */ + +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-light .gritter-close { + background-image: url(../../plugins/gritter/images/gritter-light.png) !important; +} +.gritter-item-wrapper a { + color: #18a5ed; +} +.gritter-item-wrapper a:hover { + color: #0b6694; +} +/* begin: boxed page */ +@media (min-width: 992px) { + .page-boxed { + background-color: #575757 !important; + } + .page-boxed .page-container { + background-color: #666666; + border-left: 1px solid #878787; + border-bottom: 1px solid #878787; + } + .page-boxed.page-sidebar-reversed .page-container { + border-left: 0; + border-right: 1px solid #878787; + } + .page-boxed.page-sidebar-fixed .page-container { + border-left: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-container { + border-left: 0; + border-right: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-fixed .page-sidebar { + border-left: 1px solid #878787; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #878787; + border-left: 0; + } + .page-boxed.page-sidebar-fixed.page-footer-fixed .footer { + background-color: #575757 !important; + } +} +/* end: boxed page */ +/*** +Landscape phone to portrait tablet +***/ +@media (max-width: 991px) { + /*** + page sidebar + ***/ + .page-sidebar { + background-color: #525252 !important; + } + ul.page-sidebar-menu > li > a { + border-top: 1px solid #737373 !important; + } + ul.page-sidebar-menu > li:last-child > a { + border-bottom: 0 !important; + } + .page-sidebar .sidebar-search input { + background-color: #525252 !important; + } + ul.page-sidebar-menu > li.open > a, + ul.page-sidebar-menu > li > a:hover, + ul.page-sidebar-menu > li:hover > a { + background: #474747; + } +} diff --git a/cas-server-webapp/src/main/webapp/assets/css/themes/light.css b/cas-server-webapp/src/main/webapp/assets/css/themes/light.css new file mode 100644 index 0000000..11c371e --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/themes/light.css @@ -0,0 +1,454 @@ +/*** +light theme +***/ + +/*** +Reset and overrides +***/ +body { + background-color: #fafafa !important; +} +/*** +Page header +***/ +.header { + filter: none !important; + background-image: none !important; + background-color: #434343 !important; +} +.header .btn-navbar { + background-color: #434343 !important; +} +.header .navbar-nav .dropdown-toggle:hover, +.header .navbar-nav .dropdown.open .dropdown-toggle { + background-color: #4f4f4f !important; +} +.header .navbar-nav li.dropdown .dropdown-toggle i { + color: #808080 !important; +} +/*** +Header Search +***/ +.header .search-form { + background-color: #3a3a3a; +} + +.header .search-form .form-control{ + color: #ccc; + border: 0; + background-color: #3a3a3a; +} + +.header .search-form .submit { + background: url(../../img/search-icon.png); +} +/*** +Hor menu +***/ +.header .hor-menu ul.nav li a { + color: #ccc; +} + +.header .hor-menu ul.nav li.open > a, +.header .hor-menu ul.nav li > a:hover, +.header .hor-menu ul.nav li > a:focus { + color: #fff; + background: #4f4f4f; +} + +.header .hor-menu .dropdown-menu li:hover > a, +.header .hor-menu ul.nav li.active > a, +.header .hor-menu ul.nav li.active > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu ul.nav li.current > a, +.header .hor-menu ul.nav li.current > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu .dropdown-menu { + background: #4f4f4f; +} +.header .hor-menu .dropdown-menu li > a { + color: #ccc; +} + +.header .hor-menu .hor-menu-search-form-toggler.off { + background: #4f4f4f url(../../img/hor-menu-search-close-white.png) no-repeat center; +} + +.header .hor-menu .search-form { + background:#4f4f4f; +} + +.header .hor-menu .search-form form input { + color: #ccc; +} + +.header .hor-menu .search-form .btn { + color: #ccc; + background: url(../../img/search-icon-white.png) no-repeat center; +} + +.header .hor-menu .search-form form input::-webkit-input-placeholder { /* WebKit browsers */ + color: #ccc; +} +.header .hor-menu .search-form form input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #ccc; +} +.header .hor-menu .search-form form input::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #ccc; +} +.header .hor-menu .search-form form input:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #ccc; +} + +/*** +Mega Menu +***/ +.mega-menu .mega-menu-submenu { + border-right: 1px solid #656565; +} + +.mega-menu .mega-menu-submenu li h3 { + color: #fff; +} + +/*** +Page sidebar +***/ +.page-content { + border-left: 1px solid #e2e2e2 !important; + border-bottom: 1px solid #e2e2e2 !important; +} +.page-sidebar-reversed .page-content { + border-left: 0; + border-right: 1px solid #e2e2e2 !important; +} +.page-sidebar { + background-color: #fafafa; +} +.page-sidebar-fixed .page-content { + border: 0 !important; +} +.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #e2e2e2 !important; +} + + +ul.page-sidebar-menu > li > a { + border-top: 1px solid #e2e2e2 !important; + color: #000 !important; + font-weight: 400; +} + +ul.page-sidebar-menu > li:first-child > a { + border-top: 1px solid transparent !important; +} + +ul.page-sidebar-menu > li:last-child > a { + border-bottom: 1px solid transparent !important; +} + +ul.page-sidebar-menu > li a i { + color: #bbb !important; +} +ul.page-sidebar-menu > li.open > a, +ul.page-sidebar-menu > li > a:hover, +ul.page-sidebar-menu > li:hover > a { + background: #eee; + border-top: 1px solid #e8e8e8; +} +ul.page-sidebar-menu > li.active > a .selected { + right:-7px; + top:0px; + width: 7px; + height: 39px; + background-image: url("../../img/sidebar-menu-arrow-green.png"); +} +.page-sidebar-reversed ul.page-sidebar-menu > li.active > a .selected { + right: auto; + left:-7px; + background-image: url("../../img/sidebar-menu-arrow-green-reverse.png"); +} +ul.page-sidebar-menu > li.active i { + color: #fff !important; +} +.page-sidebar-fixed ul.page-sidebar-menu > li.active > a .selected { + display: none; +} +ul.page-sidebar-menu > li.active > a{ + background: #28b779 !important; + border-top-color: transparent !important; + color:#fff !important; +} +ul.page-sidebar-menu > li.active > a i { + color: #fff; +} +ul.page-sidebar-menu > li > a > .arrow:before, +ul.page-sidebar-menu > li > a > .arrow.open:before { + color: #ccc !important; +} +ul.page-sidebar-menu > li.active > a .arrow:before, +ul.page-sidebar-menu > li.active > a .arrow.open:before { + color: #fff !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li:first-child > a { + border-top: 0px !important; +} + +ul.page-sidebar-menu ul.sub-menu > li > a { + font-weight: 400 !important; + color: #333 !important; +} +ul.page-sidebar-menu ul.sub-menu > li.active > a, +ul.page-sidebar-menu ul.sub-menu > li > a:hover { + color: #818181 !important; + background: #efefef !important; +} + +ul.page-sidebar-menu > li > ul.sub-menu a .arrow:before, +ul.page-sidebar-menu > li > ul.sub-menu a .arrow.open:before { + color: #ccc !important; +} + +/* sub menu links effects */ +ul.page-sidebar-menu ul.sub-menu > li.active > a, +ul.page-sidebar-menu ul.sub-menu > li > a:hover, +ul.page-sidebar-menu ul.sub-menu > li.open > a { + color: #818181 !important; + background: #efefef !important; +} +ul.page-sidebar-menu ul.sub-menu > li > a i { + color: #bbb !important; +} + +/* sidebar search */ +.page-sidebar .sidebar-search input { + background-color: #fbfbfb !important; + color: #727272 !important; +} +.page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #aaa !important; +} +.page-sidebar .sidebar-search input:-moz-placeholder { + color: #aaa !important; +} +.page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #aaa !important; +} +.page-sidebar .sidebar-search .input-box { + border-bottom: 1px solid #e2e2e2 !important; +} +.page-sidebar .sidebar-search .submit { + background-image: url(../../img/search-icon-white.png); +} + +/*** +Sidebar toggler +***/ +.sidebar-toggler { + background-image: url(../../img/sidebar-toggler-light.jpg); + background-color: #333; +} +/* search box bg color on expanded */ +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + background-color: #fbfbfb !important; +} +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-image: url("../../img/sidebar-search-close-light.png"); +} +/* sub menu bg color on hover menu item */ +.page-sidebar-closed ul.page-sidebar-menu > li:hover .sub-menu { + background-color: #fbfbfb; +} +/*** +Footer +***/ +.footer .footer-inner { + color: #333333; +} +.footer .footer-tools .go-top { + background-color: #666666; +} +.footer .footer-tools .go-top:hover { + opacity: 0.7; + filter: alpha(opacity=70); +} +.footer .footer-tools .go-top i { + color: #999999; +} +/*** +Footer Layouts (new in v1.3) +***/ +/* begin:fixed footer */ +.page-footer-fixed .footer { + background-color: #434343; +} +.page-footer-fixed .footer .footer-inner { + color: #aaaaaa; +} +.page-footer-fixed .footer .footer-tools .go-top { + background-color: #666666; +} +.page-footer-fixed .footer .footer-tools .go-top i { + color: #aaaaaa; +} +/* end:fixed footer */ +/*** +Gritter Notifications +***/ +.gritter-top { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left -30px !important; +} +.gritter-bottom { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left bottom !important; +} +.gritter-item { + display: block; + background: url(../../plugins/gritter/images/gritter.png) no-repeat left -40px !important; +} +.gritter-close { + background: url(../../plugins/gritter/images/gritter.png) no-repeat left top !important; +} +.gritter-title { + text-shadow: none !important; + /* Not supported by IE :( */ + +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-light .gritter-close { + background-image: url(../../plugins/gritter/images/gritter-light.png) !important; +} +.gritter-item-wrapper a { + color: #18a5ed; +} +.gritter-item-wrapper a:hover { + color: #0b6694; +} +/* begin: boxed page */ +@media (min-width: 992px) { + .page-boxed { + background-color: #E8E8E8 !important; + } + .page-boxed .page-container { + background-color: #fafafa; + border-left: 1px solid #e2e2e2; + border-bottom: 1px solid #e2e2e2; + } + .page-sidebar-reversed.page-boxed .page-container { + border-left: 0; + border-right: 1px solid #e2e2e2; + } + .page-boxed.page-sidebar-fixed .page-container { + border-left: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-fixed .page-sidebar { + border-left: 1px solid #e2e2e2; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #e2e2e2; + border-left: 0; + } + .page-boxed.page-sidebar-fixed.page-footer-fixed .footer { + background-color: #E8E8E8 !important; + } +} +/* end: boxed page */ +/*** +Landscape phone to portrait tablet +***/ +@media (max-width: 991px) { + /*** + page sidebar + ***/ + .page-sidebar { + background-color: #f1f1f1 !important; + border-right: none !important; + } + .page-sidebar-fixed .page-sidebar { + border-right: none !important; + } + .page-content { + border-left: none !important; + } + ul.page-sidebar-menu > li > a { + border-top: 1px solid #ccc !important; + } + ul.page-sidebar-menu > li:last-child > a { + border-bottom: 0 !important; + } + + ul.page-sidebar-menu > li.open > a, + ul.page-sidebar-menu > li > a:hover { + color: #666666 !important; + background-color: #dddddd !important; + } + ul.page-sidebar-menu > li.open > a { + border-bottom-color: transparent !important; + } + ul.page-sidebar-menu > li.active > a { + color: #ffffff !important; + background-color: #28b779 !important; + } + + ul.page-sidebar-menu ul.sub-menu > li > a { + color: #111 !important; + } + + ul.page-sidebar-menu ul.sub-menu > li.open > a, + ul.page-sidebar-menu ul.sub-menu > li.active > a, + ul.page-sidebar-menu ul.sub-menu > li > a:hover { + color: #666666 !important; + background: #dddddd !important; + } + + .page-sidebar .sidebar-search input { + background-color: #f1f1f1 !important; + color: #ccc !important; + } + + .page-sidebar .sidebar-search .input-box { + border-bottom-color: #ccc !important; + } + .page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #ccc !important; + } + .page-sidebar .sidebar-search input:-moz-placeholder { + color: #ccc !important; + } + .page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #ccc !important; + } + + /*** + page footer + ***/ + + .footer { + background-color: #434343; + } + + .footer .footer-inner { + color: #cccccc; + } + .footer .footer-tools .go-top { + background-color: #666666; + } + .footer .footer-tools .go-top i { + color: #999999; + } +} + +@media (max-width: 767px) { + body { + background-color: #333 !important; + } +} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/css/themes/purple.css b/cas-server-webapp/src/main/webapp/assets/css/themes/purple.css new file mode 100644 index 0000000..4ba9a08 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/css/themes/purple.css @@ -0,0 +1,354 @@ +/*** +Purple theme +***/ +/*** +Reset and overrides +***/ +body { + background-color: #701584 !important; +} +/*** +Page header +***/ +.header { + filter: none !important; + background-image: none !important; + background-color: #470d54 !important; +} +.header .btn-navbar { + background-color: #470d54 !important; +} +.header .navbar-nav .dropdown-toggle:hover, +.header .navbar-nav .dropdown.open .dropdown-toggle { + background-color: #69147b !important; +} +.header .navbar-nav li.dropdown .dropdown-toggle i { + color: #c84fe3 !important; +} +/*** +Header Search +***/ +.header .search-form { + background-color: #360A40; +} + +.header .search-form .form-control{ + color: #ccc; + border: 0; + background-color: #360A40; +} + +.header .search-form .submit { + background: url(../../img/search-icon-purple.png); +} +/*** +Hor menu +***/ +.header .hor-menu ul.nav li a { + color: #ccc; +} + +.header .hor-menu ul.nav li.open > a, +.header .hor-menu ul.nav li > a:hover, +.header .hor-menu ul.nav li > a:focus { + color: #fff; + background: #69147b; +} + +.header .hor-menu .dropdown-menu li:hover > a, +.header .hor-menu ul.nav li.active > a, +.header .hor-menu ul.nav li.active > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu ul.nav li.current > a, +.header .hor-menu ul.nav li.current > a:hover { + color: #fff; + background: #e02222 !important; +} + +.header .hor-menu .dropdown-menu { + background: #69147b; +} +.header .hor-menu .dropdown-menu li > a { + color: #ccc; +} + +.header .hor-menu .hor-menu-search-form-toggler.off { + background: #69147b url(../../img/hor-menu-search-close-white.png) no-repeat center; +} + +.header .hor-menu .search-form { + background:#69147b; +} + +.header .hor-menu .search-form form input { + color: #ccc; +} + +.header .hor-menu .search-form .btn { + color: #ccc; + background: url(../../img/search-icon-white.png) no-repeat center; +} + +.header .hor-menu .search-form form input::-webkit-input-placeholder { /* WebKit browsers */ + color: #ccc; +} +.header .hor-menu .search-form form input:-moz-placeholder { /* Mozilla Firefox 4 to 18 */ + color: #ccc; +} +.header .hor-menu .search-form form input::-moz-placeholder { /* Mozilla Firefox 19+ */ + color: #ccc; +} +.header .hor-menu .search-form form input:-ms-input-placeholder { /* Internet Explorer 10+ */ + color: #ccc; +} + +/*** +Mega Menu +***/ +.mega-menu .mega-menu-submenu { + border-right: 1px solid #78188C; +} + +.mega-menu .mega-menu-submenu li h3 { + color: #fff; +} + +/*** +Page sidebar +***/ +.page-sidebar { + background-color: #701584; +} +ul.page-sidebar-menu > li > a { + border-top: 1px solid #9d1db9 !important; + color: #ffffff !important; +} +ul.page-sidebar-menu > li:last-child > a { + border-bottom: 1px solid transparent !important; +} +ul.page-sidebar-menu > li a i { + color: #cf65e7; +} +ul.page-sidebar-menu > li.open > a, +ul.page-sidebar-menu > li > a:hover, +ul.page-sidebar-menu > li:hover > a { + background: #5d116e; +} +ul.page-sidebar-menu > li.active > a { + background: #571067 !important; + border-top-color: transparent !important; + color: #ffffff; +} +ul.page-sidebar-menu > li.active > a i { + color: #ffffff; +} +ul.page-sidebar-menu > li > ul.sub-menu > li:first-child > a { + border-top: 0px !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + color: #ffffff !important; + background: #951cb0 !important; +} +ul.page-sidebar-menu > li > ul.sub-menu > li > a:hover { + background: #951cb0 !important; +} +/* 3rd level sub menu */ +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li.active > a, +ul.page-sidebar-menu > li > ul.sub-menu li > ul.sub-menu > li > a:hover, +ul.page-sidebar-menu > li > ul.sub-menu li.open > a { + color: #ffffff !important; + background: #951cb0 !important; +} +/* font color for all sub menu links*/ +ul.page-sidebar-menu li > ul.sub-menu > li > a { + color: #e4a7f1; +} +/* menu arrows */ +ul.page-sidebar-menu > li > a .arrow:before, +ul.page-sidebar-menu > li > a .arrow.open:before { + color: #c239df !important; +} +ul.page-sidebar-menu > li > ul.sub-menu a .arrow:before, +ul.page-sidebar-menu > li > ul.sub-menu a .arrow.open:before { + color: #bb23dc !important; +} +ul.page-sidebar-menu > li > a > .arrow.open:before { + color: #c84fe3 !important; +} +ul.page-sidebar-menu > li.active > a .arrow:before, +ul.page-sidebar-menu > li.active > a .arrow.open:before { + color: #ffffff !important; +} +/* sidebar search */ +.page-sidebar .sidebar-search input { + background-color: #4b0e58 !important; + color: #bf55d7; +} +.page-sidebar .sidebar-search input::-webkit-input-placeholder { + color: #b84dd0 !important; +} +.page-sidebar .sidebar-search input:-moz-placeholder { + color: #b84dd0 !important; +} +.page-sidebar .sidebar-search input:-ms-input-placeholder { + color: #b84dd0 !important; +} +.page-sidebar .sidebar-search input { + background-color: #701584 !important; + color: #bfbfbf !important; +} +.page-sidebar .sidebar-search .input-box { + border-bottom: 1px solid #a93bc1 !important; +} +.page-sidebar .sidebar-search .submit { + background-image: url(../../img/search-icon-purple.png); +} +/*** +Sidebar toggler +***/ +.sidebar-toggler { + background-image: url(../../img/sidebar-toggler-purple.jpg); + background-color: #4b0e58; +} +/* search box bg color on expanded */ +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container { + background-color: #701584 !important; +} +.page-sidebar-closed .page-sidebar .sidebar-search.open .form-container .remove { + background-image: url("../../img/sidebar-search-close-purple.png"); +} +/* sub menu bg color on hover menu item */ +.page-sidebar-closed ul.page-sidebar-menu > li:hover .sub-menu { + background-color: #701584; +} +/*** +Footer +***/ +.footer .footer-inner { + color: #c84fe3; +} +.footer .footer-tools .go-top { + background-color: #8a1aa3; +} +.footer .footer-tools .go-top:hover { + opacity: 0.7; + filter: alpha(opacity=70); +} +.footer .footer-tools .go-top i { + color: #c84fe3; +} +/*** +Footer Layouts (new in v1.3) +***/ +/* begin:fixed footer */ +.page-footer-fixed .footer { + background-color: #4b0e58; +} +.page-footer-fixed .footer .footer-inner { + color: #c84fe3; +} +.page-footer-fixed .footer .footer-tools .go-top { + background-color: #8a1aa3; +} +.page-footer-fixed .footer .footer-tools .go-top i { + color: #c84fe3; +} +/* end:fixed footer */ +/*** +Gritter Notifications +***/ +.gritter-top { + background: url(../../plugins/gritter/images/gritter-purple.png) no-repeat left -30px !important; +} +.gritter-bottom { + background: url(../../plugins/gritter/images/gritter-purple.png) no-repeat left bottom !important; +} +.gritter-item { + display: block; + background: url(../../plugins/gritter/images/gritter-purple.png) no-repeat left -40px !important; +} +.gritter-close { + background: url(../../plugins/gritter/images/gritter-purple.png) no-repeat left top !important; +} +.gritter-title { + text-shadow: none !important; + /* Not supported by IE :( */ + +} +/* for the light (white) version of the gritter notice */ +.gritter-light .gritter-item, +.gritter-light .gritter-bottom, +.gritter-light .gritter-top, +.gritter-light .gritter-close { + background-image: url(../../plugins/gritter/images/gritter-light.png) !important; +} +.gritter-item-wrapper a { + color: #18a5ed; +} +.gritter-item-wrapper a:hover { + color: #0b6694; +} +/* begin: boxed page */ +@media (min-width: 992px) { + .page-boxed { + background-color: #5a116a !important; + } + .page-boxed .page-container { + background-color: #701584; + border-left: 1px solid #a11ebd; + border-bottom: 1px solid #a11ebd; + } + .page-boxed.page-sidebar-reversed .page-container { + border-left: 0; + border-right: 1px solid #a11ebd; + } + .page-boxed.page-sidebar-fixed .page-container { + border-left: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-container { + border-left: 0; + border-right: 0; + border-bottom: 0; + } + .page-boxed.page-sidebar-fixed .page-sidebar { + border-left: 1px solid #a11ebd; + } + .page-boxed.page-sidebar-reversed.page-sidebar-fixed .page-sidebar { + border-right: 1px solid #a11ebd; + border-left: 0; + } + .page-boxed.page-sidebar-fixed.page-footer-fixed .footer { + background-color: #5a116a !important; + } +} +/* end: boxed page */ +/*** +Landscape phone to portrait tablet +***/ +@media (max-width: 991px) { + /*** + page sidebar + ***/ + .page-sidebar { + background-color: #520f61 !important; + } + ul.page-sidebar-menu > li > a { + border-top: 1px solid #83189a !important; + } + ul.page-sidebar-menu > li:last-child > a { + border-bottom: 0 !important; + } + .page-sidebar .sidebar-search input { + background-color: #520f61 !important; + } + ul.page-sidebar-menu > li.open > a, + ul.page-sidebar-menu > li > a:hover, + ul.page-sidebar-menu > li:hover > a { + background: #430d4f; + } +} diff --git a/cas-server-webapp/src/main/webapp/assets/img/bg-white-lock.png b/cas-server-webapp/src/main/webapp/assets/img/bg-white-lock.png new file mode 100644 index 0000000..1b8a1f7 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/bg-white-lock.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/bg/1.jpg b/cas-server-webapp/src/main/webapp/assets/img/bg/1.jpg new file mode 100644 index 0000000..7ee6694 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/bg/1.jpg differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/bg/2.jpg b/cas-server-webapp/src/main/webapp/assets/img/bg/2.jpg new file mode 100644 index 0000000..094e2f2 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/bg/2.jpg differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/bg/3.jpg b/cas-server-webapp/src/main/webapp/assets/img/bg/3.jpg new file mode 100644 index 0000000..6abb77a Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/bg/3.jpg differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/bg/4.jpg b/cas-server-webapp/src/main/webapp/assets/img/bg/4.jpg new file mode 100644 index 0000000..8bbfad7 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/bg/4.jpg differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/input-spinner.gif b/cas-server-webapp/src/main/webapp/assets/img/input-spinner.gif new file mode 100644 index 0000000..5b33f7e Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/input-spinner.gif differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/logo-big.png b/cas-server-webapp/src/main/webapp/assets/img/logo-big.png new file mode 100644 index 0000000..c8f832d Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/logo-big.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/logo.png b/cas-server-webapp/src/main/webapp/assets/img/logo.png new file mode 100644 index 0000000..cf748c8 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/logo.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/overlay-icon.png b/cas-server-webapp/src/main/webapp/assets/img/overlay-icon.png new file mode 100644 index 0000000..ecdb629 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/overlay-icon.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-collapse-icon-white.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-collapse-icon-white.png new file mode 100644 index 0000000..a415159 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-collapse-icon-white.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-collapse-icon.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-collapse-icon.png new file mode 100644 index 0000000..5f4901f Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-collapse-icon.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-config-icon-white.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-config-icon-white.png new file mode 100644 index 0000000..2f3a272 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-config-icon-white.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-config-icon.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-config-icon.png new file mode 100644 index 0000000..f045121 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-config-icon.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-expand-icon-white.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-expand-icon-white.png new file mode 100644 index 0000000..f2ecf78 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-expand-icon-white.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-expand-icon.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-expand-icon.png new file mode 100644 index 0000000..54b891b Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-expand-icon.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-reload-icon-white.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-reload-icon-white.png new file mode 100644 index 0000000..a14730b Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-reload-icon-white.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-reload-icon.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-reload-icon.png new file mode 100644 index 0000000..bdb0f08 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-reload-icon.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-remove-icon-white.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-remove-icon-white.png new file mode 100644 index 0000000..ddc6d2c Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-remove-icon-white.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/portlet-remove-icon.png b/cas-server-webapp/src/main/webapp/assets/img/portlet-remove-icon.png new file mode 100644 index 0000000..e2a02c6 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/portlet-remove-icon.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/remove-icon-small.png b/cas-server-webapp/src/main/webapp/assets/img/remove-icon-small.png new file mode 100644 index 0000000..382bb24 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/remove-icon-small.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/sidebar-menu-arrow-reverse.png b/cas-server-webapp/src/main/webapp/assets/img/sidebar-menu-arrow-reverse.png new file mode 100644 index 0000000..c5a8914 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/sidebar-menu-arrow-reverse.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/sidebar-menu-arrow.png b/cas-server-webapp/src/main/webapp/assets/img/sidebar-menu-arrow.png new file mode 100644 index 0000000..e00acb1 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/sidebar-menu-arrow.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/syncfusion-icons-white.png b/cas-server-webapp/src/main/webapp/assets/img/syncfusion-icons-white.png new file mode 100644 index 0000000..625dcc0 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/syncfusion-icons-white.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/img/syncfusion-icons.png b/cas-server-webapp/src/main/webapp/assets/img/syncfusion-icons.png new file mode 100644 index 0000000..7ee6873 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/img/syncfusion-icons.png differ diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/backstretch/jquery.backstretch.js b/cas-server-webapp/src/main/webapp/assets/plugins/backstretch/jquery.backstretch.js new file mode 100644 index 0000000..effee3a --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/backstretch/jquery.backstretch.js @@ -0,0 +1,357 @@ +/*! Backstretch - v2.0.3 - 2012-11-30 +* http://srobbin.com/jquery-plugins/backstretch/ +* Copyright (c) 2012 Scott Robbin; Licensed MIT */ + +;(function ($, window, undefined) { + 'use strict'; + + /* PLUGIN DEFINITION + * ========================= */ + + $.fn.backstretch = function (images, options) { + // We need at least one image + if (images === undefined || images.length === 0) { + $.error("No images were supplied for Backstretch"); + } + + /* + * Scroll the page one pixel to get the right window height on iOS + * Pretty harmless for everyone else + */ + if ($(window).scrollTop() === 0 ) { + window.scrollTo(0, 0); + } + + return this.each(function () { + var $this = $(this) + , obj = $this.data('backstretch'); + + // If we've already attached Backstretch to this element, remove the old instance. + if (obj) { + // Merge the old options with the new + options = $.extend(obj.options, options); + + // Remove the old instance + obj.destroy(true); + } + + obj = new Backstretch(this, images, options); + $this.data('backstretch', obj); + }); + }; + + // If no element is supplied, we'll attach to body + $.backstretch = function (images, options) { + // Return the instance + return $('body') + .backstretch(images, options) + .data('backstretch'); + }; + + // Custom selector + $.expr[':'].backstretch = function(elem) { + return $(elem).data('backstretch') !== undefined; + }; + + /* DEFAULTS + * ========================= */ + + $.fn.backstretch.defaults = { + centeredX: true // Should we center the image on the X axis? + , centeredY: true // Should we center the image on the Y axis? + , duration: 5000 // Amount of time in between slides (if slideshow) + , fade: 0 // Speed of fade transition between slides + }; + + /* STYLES + * + * Baked-in styles that we'll apply to our elements. + * In an effort to keep the plugin simple, these are not exposed as options. + * That said, anyone can override these in their own stylesheet. + * ========================= */ + var styles = { + wrap: { + left: 0 + , top: 0 + , overflow: 'hidden' + , margin: 0 + , padding: 0 + , height: '100%' + , width: '100%' + , zIndex: -999999 + } + , img: { + position: 'absolute' + , display: 'none' + , margin: 0 + , padding: 0 + , border: 'none' + , width: 'auto' + , height: 'auto' + , maxWidth: 'none' + , zIndex: -999999 + } + }; + + /* CLASS DEFINITION + * ========================= */ + var Backstretch = function (container, images, options) { + this.options = $.extend({}, $.fn.backstretch.defaults, options || {}); + + /* In its simplest form, we allow Backstretch to be called on an image path. + * e.g. $.backstretch('/path/to/image.jpg') + * So, we need to turn this back into an array. + */ + this.images = $.isArray(images) ? images : [images]; + + // Preload images + $.each(this.images, function () { + $('')[0].src = this; + }); + + // Convenience reference to know if the container is body. + this.isBody = container === document.body; + + /* We're keeping track of a few different elements + * + * Container: the element that Backstretch was called on. + * Wrap: a DIV that we place the image into, so we can hide the overflow. + * Root: Convenience reference to help calculate the correct height. + */ + this.$container = $(container); + this.$wrap = $('
').css(styles.wrap).appendTo(this.$container); + this.$root = this.isBody ? supportsFixedPosition ? $(window) : $(document) : this.$container; + + // Non-body elements need some style adjustments + if (!this.isBody) { + // If the container is statically positioned, we need to make it relative, + // and if no zIndex is defined, we should set it to zero. + var position = this.$container.css('position') + , zIndex = this.$container.css('zIndex'); + + this.$container.css({ + position: position === 'static' ? 'relative' : position + , zIndex: zIndex === 'auto' ? 0 : zIndex + , background: 'none' + }); + + // Needs a higher z-index + this.$wrap.css({zIndex: -999998}); + } + + // Fixed or absolute positioning? + this.$wrap.css({ + position: this.isBody && supportsFixedPosition ? 'fixed' : 'absolute' + }); + + // Set the first image + this.index = 0; + this.show(this.index); + + // Listen for resize + $(window).on('resize.backstretch', $.proxy(this.resize, this)) + .on('orientationchange.backstretch', $.proxy(function () { + // Need to do this in order to get the right window height + if (this.isBody && window.pageYOffset === 0) { + window.scrollTo(0, 1); + this.resize(); + } + }, this)); + }; + + /* PUBLIC METHODS + * ========================= */ + Backstretch.prototype = { + resize: function () { + try { + var bgCSS = {left: 0, top: 0} + , rootWidth = this.isBody ? this.$root.width() : this.$root.innerWidth() + , bgWidth = rootWidth + , rootHeight = this.isBody ? ( window.innerHeight ? window.innerHeight : this.$root.height() ) : this.$root.innerHeight() + , bgHeight = bgWidth / this.$img.data('ratio') + , bgOffset; + + // Make adjustments based on image ratio + if (bgHeight >= rootHeight) { + bgOffset = (bgHeight - rootHeight) / 2; + if(this.options.centeredY) { + bgCSS.top = '-' + bgOffset + 'px'; + } + } else { + bgHeight = rootHeight; + bgWidth = bgHeight * this.$img.data('ratio'); + bgOffset = (bgWidth - rootWidth) / 2; + if(this.options.centeredX) { + bgCSS.left = '-' + bgOffset + 'px'; + } + } + + this.$wrap.css({width: rootWidth, height: rootHeight}) + .find('img:not(.deleteable)').css({width: bgWidth, height: bgHeight}).css(bgCSS); + } catch(err) { + // IE7 seems to trigger resize before the image is loaded. + // This try/catch block is a hack to let it fail gracefully. + } + + return this; + } + + // Show the slide at a certain position + , show: function (index) { + // Validate index + if (Math.abs(index) > this.images.length - 1) { + return; + } else { + this.index = index; + } + + // Vars + var self = this + , oldImage = self.$wrap.find('img').addClass('deleteable') + , evt = $.Event('backstretch.show', { + relatedTarget: self.$container[0] + }); + + // Pause the slideshow + clearInterval(self.interval); + + // New image + self.$img = $('') + .css(styles.img) + .bind('load', function (e) { + var imgWidth = this.width || $(e.target).width() + , imgHeight = this.height || $(e.target).height(); + + // Save the ratio + $(this).data('ratio', imgWidth / imgHeight); + + // Show the image, then delete the old one + // "speed" option has been deprecated, but we want backwards compatibilty + $(this).fadeIn(self.options.speed || self.options.fade, function () { + oldImage.remove(); + + // Resume the slideshow + if (!self.paused) { + self.cycle(); + } + + // Trigger the event + self.$container.trigger(evt, self); + }); + + // Resize + self.resize(); + }) + .appendTo(self.$wrap); + + // Hack for IE img onload event + self.$img.attr('src', self.images[index]); + return self; + } + + , next: function () { + // Next slide + return this.show(this.index < this.images.length - 1 ? this.index + 1 : 0); + } + + , prev: function () { + // Previous slide + return this.show(this.index === 0 ? this.images.length - 1 : this.index - 1); + } + + , pause: function () { + // Pause the slideshow + this.paused = true; + return this; + } + + , resume: function () { + // Resume the slideshow + this.paused = false; + this.next(); + return this; + } + + , cycle: function () { + // Start/resume the slideshow + if(this.images.length > 1) { + // Clear the interval, just in case + clearInterval(this.interval); + + this.interval = setInterval($.proxy(function () { + // Check for paused slideshow + if (!this.paused) { + this.next(); + } + }, this), this.options.duration); + } + return this; + } + + , destroy: function (preserveBackground) { + // Stop the resize events + $(window).off('resize.backstretch orientationchange.backstretch'); + + // Clear the interval + clearInterval(this.interval); + + // Remove Backstretch + if(!preserveBackground) { + this.$wrap.remove(); + } + this.$container.removeData('backstretch'); + } + }; + + /* SUPPORTS FIXED POSITION? + * + * Based on code from jQuery Mobile 1.1.0 + * http://jquerymobile.com/ + * + * In a nutshell, we need to figure out if fixed positioning is supported. + * Unfortunately, this is very difficult to do on iOS, and usually involves + * injecting content, scrolling the page, etc.. It's ugly. + * jQuery Mobile uses this workaround. It's not ideal, but works. + * + * Modified to detect IE6 + * ========================= */ + + var supportsFixedPosition = (function () { + var ua = navigator.userAgent + , platform = navigator.platform + // Rendering engine is Webkit, and capture major version + , wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ) + , wkversion = !!wkmatch && wkmatch[ 1 ] + , ffmatch = ua.match( /Fennec\/([0-9]+)/ ) + , ffversion = !!ffmatch && ffmatch[ 1 ] + , operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ) + , omversion = !!operammobilematch && operammobilematch[ 1 ] + , iematch = ua.match( /MSIE ([0-9]+)/ ) + , ieversion = !!iematch && iematch[ 1 ]; + + return !( + // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5) + ((platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534) || + + // Opera Mini + (window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]") || + (operammobilematch && omversion < 7458) || + + //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2) + (ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533) || + + // Firefox Mobile before 6.0 - + (ffversion && ffversion < 6) || + + // WebOS less than 3 + ("palmGetResource" in window && wkversion && wkversion < 534) || + + // MeeGo + (ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1) || + + // IE6 + (ieversion && ieversion <= 6) + ); + }()); + +}(jQuery, window)); \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/backstretch/jquery.backstretch.min.js b/cas-server-webapp/src/main/webapp/assets/plugins/backstretch/jquery.backstretch.min.js new file mode 100644 index 0000000..874c1a6 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/backstretch/jquery.backstretch.min.js @@ -0,0 +1,4 @@ +/*! Backstretch - v2.0.3 - 2012-11-30 +* http://srobbin.com/jquery-plugins/backstretch/ +* Copyright (c) 2012 Scott Robbin; Licensed MIT */ +(function(e,t,n){"use strict";e.fn.backstretch=function(r,s){return(r===n||r.length===0)&&e.error("No images were supplied for Backstretch"),e(t).scrollTop()===0&&t.scrollTo(0,0),this.each(function(){var t=e(this),n=t.data("backstretch");n&&(s=e.extend(n.options,s),n.destroy(!0)),n=new i(this,r,s),t.data("backstretch",n)})},e.backstretch=function(t,n){return e("body").backstretch(t,n).data("backstretch")},e.expr[":"].backstretch=function(t){return e(t).data("backstretch")!==n},e.fn.backstretch.defaults={centeredX:!0,centeredY:!0,duration:5e3,fade:0};var r={wrap:{left:0,top:0,overflow:"hidden",margin:0,padding:0,height:"100%",width:"100%",zIndex:-999999},img:{position:"absolute",display:"none",margin:0,padding:0,border:"none",width:"auto",height:"auto",maxWidth:"none",zIndex:-999999}},i=function(n,i,o){this.options=e.extend({},e.fn.backstretch.defaults,o||{}),this.images=e.isArray(i)?i:[i],e.each(this.images,function(){e("")[0].src=this}),this.isBody=n===document.body,this.$container=e(n),this.$wrap=e('
').css(r.wrap).appendTo(this.$container),this.$root=this.isBody?s?e(t):e(document):this.$container;if(!this.isBody){var u=this.$container.css("position"),a=this.$container.css("zIndex");this.$container.css({position:u==="static"?"relative":u,zIndex:a==="auto"?0:a,background:"none"}),this.$wrap.css({zIndex:-999998})}this.$wrap.css({position:this.isBody&&s?"fixed":"absolute"}),this.index=0,this.show(this.index),e(t).on("resize.backstretch",e.proxy(this.resize,this)).on("orientationchange.backstretch",e.proxy(function(){this.isBody&&t.pageYOffset===0&&(t.scrollTo(0,1),this.resize())},this))};i.prototype={resize:function(){try{var e={left:0,top:0},n=this.isBody?this.$root.width():this.$root.innerWidth(),r=n,i=this.isBody?t.innerHeight?t.innerHeight:this.$root.height():this.$root.innerHeight(),s=r/this.$img.data("ratio"),o;s>=i?(o=(s-i)/2,this.options.centeredY&&(e.top="-"+o+"px")):(s=i,r=s*this.$img.data("ratio"),o=(r-n)/2,this.options.centeredX&&(e.left="-"+o+"px")),this.$wrap.css({width:n,height:i}).find("img:not(.deleteable)").css({width:r,height:s}).css(e)}catch(u){}return this},show:function(t){if(Math.abs(t)>this.images.length-1)return;this.index=t;var n=this,i=n.$wrap.find("img").addClass("deleteable"),s=e.Event("backstretch.show",{relatedTarget:n.$container[0]});return clearInterval(n.interval),n.$img=e("").css(r.img).bind("load",function(t){var r=this.width||e(t.target).width(),o=this.height||e(t.target).height();e(this).data("ratio",r/o),e(this).fadeIn(n.options.speed||n.options.fade,function(){i.remove(),n.paused||n.cycle(),n.$container.trigger(s,n)}),n.resize()}).appendTo(n.$wrap),n.$img.attr("src",n.images[t]),n},next:function(){return this.show(this.index1&&(clearInterval(this.interval),this.interval=setInterval(e.proxy(function(){this.paused||this.next()},this),this.options.duration)),this},destroy:function(n){e(t).off("resize.backstretch orientationchange.backstretch"),clearInterval(this.interval),n||this.$wrap.remove(),this.$container.removeData("backstretch")}};var s=function(){var e=navigator.userAgent,n=navigator.platform,r=e.match(/AppleWebKit\/([0-9]+)/),i=!!r&&r[1],s=e.match(/Fennec\/([0-9]+)/),o=!!s&&s[1],u=e.match(/Opera Mobi\/([0-9]+)/),a=!!u&&u[1],f=e.match(/MSIE ([0-9]+)/),l=!!f&&f[1];return!((n.indexOf("iPhone")>-1||n.indexOf("iPad")>-1||n.indexOf("iPod")>-1)&&i&&i<534||t.operamini&&{}.toString.call(t.operamini)==="[object OperaMini]"||u&&a<7458||e.indexOf("Android")>-1&&i&&i<533||o&&o<6||"palmGetResource"in t&&i&&i<534||e.indexOf("MeeGo")>-1&&e.indexOf("NokiaBrowser/8.5.0")>-1||l&&l<=6)}()})(jQuery,window); \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/LICENSE b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/LICENSE new file mode 100644 index 0000000..d92b1dd --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/LICENSE @@ -0,0 +1,24 @@ + +The MIT License (MIT) + +Copyright (c) 2012-2013 Cameron Spear http://cameronspear.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/README.md b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/README.md new file mode 100644 index 0000000..c30c975 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/README.md @@ -0,0 +1,121 @@ +Bootstrap Hover Dropdown Plugin +=============================== + +**Version 2.0.1** + +#### Name Change + +*Twitter Bootstrap* is now just *Bootstrap*, and I've renamed this repo, **renamed the files** and change all references from *Twitter Bootstrap* to just *Bootstrap* in the docs/example to reflect that. + +No actual code changed, so I am keeping the current version (`2.0.1` at the time of this writing), but be aware of the lack of *twitter-* from the beginning of the JS files and how that might affect you. + +Sorry for any inconvenience! + +#### Updated for Bootstrap 3 + +I updated the demo with Bootstrap 3, as well as removed code associated to submenus ([not supported in Bootstrap 3](https://github.com/twbs/bootstrap/pull/6342#issuecomment-11594010)) and touch devices (just make sure you have `data-toggle="dropdown"` to let Mobile do its thing and my plugin won't interfere). + +## Introduction + +A simple plugin to enable Bootstrap dropdowns to activate on hover and provide a nice user experience. + +The dropdowns are dismissed after a configurable delay. This fixes an issue that can instantly close your nav because of a 1px gap between the button/nav item that activated the dropdown and the actual dropdown. It is also generally a better user experience, as users are not punished by going 1 pixel outside of the dropdown, which would instantly close the nav without a delay. + +**Note:** The HTML markup is the same as with any other Bootstrap dropdown. This will not interfere with Bootstrap's default activate-on-click method (i.e. this plugin combined with Bootstrap's default behavior work well to support both the ideal experience on desktop and mobile). + +## Installation + +You can simply download and extract the package downloaded from GitHub. Alternatively, you can download the files via [Bower](http://bower.io/) (a JavaScript package management system): + +``` +bower install bootstrap-hover-dropdown +``` + +which will also automatically install Bootstrap and jQuery if needed. + +Once you have the files downloaded, link to the files in your code *after* you include the main Bootstrap JS file(s): + +```html + + + + +``` + +## Usage + +Just like in Bootstrap you can activate it without any JavaScript, just by adding a data-attribute, you can make it automatically work. + +Add `data-hover="dropdown"` in addition (or in place of) Bootstrap's `data-toggle="dropdown"`. + +You can set options via data-attributes, too, via `data-delay` and `data-close-others`. Here's an example of markup: + +```html + +``` + +Alternatively, you can initialize via JavaScript: + +```javascript +$('.dropdown-toggle').dropdownHover(options); +``` + +This also works with submenus without any other configuring since Bootstrap already supports this feature. Just use the markup like you were using before. Only the top level anchor tag needs any special markup for my plugin to work (see demo for proper markup). + +## Options + +* **delay**: *(optional)* The delay in miliseconds. This is the time to wait before closing a dropdown when the mouse is no longer over the dropdown or the button/nav item that activated it. Defaults to `500`. +* **instantlyCloseOthers**: *(optional)* A boolean value that when true, will instantly close all other dropdowns matched by the selector used when you activate a new navigation. This is nice for when you have dropdowns close together that may overlap. Default is `true`. + +## Demo + +You can view a demo for this plugin on my site: http://cameronspear.com/demos/bootstrap-hover-dropdown/ + +### A Note on Choosing a Selector + +This plugin purposedly lets you choose a selector (as opposed to apply this to everything with the class of `.dropdown-toggle`). This is so that you can selectively apply it where you want. Maybe you only want to use it for the main nav, and not have it activate for dropdown buttons in the main content. You can add a class to the item that normally gets `.dropdown-toggle` and use that class with this plugin to easily achieve that, or use a selector such as `.main-nav .dropdown-toggle`. + +**Important:** Bootstrap relies on styles associated with the class `.dropdown-toggle` (for stuff like the caret color), and it is recommended you leave that class alone. + +## Changes/Bug Fixes + +I'm a slacker and only started keeping track of changes/bug fixes starting in March of 2013. + +* **2013-12-05** Change all references of *Twitter Bootstrap* to *Bootstrap* to reflect Bootstrap's name change. +* **2013-11-09** Disable this plugin for devices that support touch. The plugin was causing issues with some mobile devices, and it's not necessary for them. +* **2013-08-02** Add support for Bootstrap 3. For Bootstrap 2.x.x, use the `bootstrap-2.x.x` branch. +* **2013-06-10** Always instantly close submenu siblings when opening a new one. Issue #19. +* **2013-06-10** A fix for my last fix that would sometimes cause the correct item to not trigger when it should. Issue #18. +* **2013-05-08** Fix issue where a sibling could open a drop down that wasn't theirs. Issue #18. +* **2013-04-29** Added support for submenus: Submenus should now honor the delay option and way before closing. They do not abide by the `instantlyCloseOthers` option, as it's not really relevant. +* **2013-04-19** Fixed an issue where the conditional rule to disable hover on mobile wasn't working if you included the script in the header. +* **2013-04-03** Made it so if you're using the responsive CSS and in tablet/mobile view, disable the hover. +* **2013-03-16** Fixed an issue where the options you passed in via the method call were completely ignored. + +## Contributions + +[Mattia Larentis](https://github.com/nostalgiaz) helped me with the idea for the data-attributes and doing the options via an object. + +## Roadmap + +As this plugin, in its simplicity, is pretty much exactly what I intend it to be, I don't plan to implement any new features. ~~**One exception:** I would like to tweak it so that when you're in a submenu, it doesn't instantly close when you hover outside of it.~~ **Update:** I added this in late April 2013. + +If you have ideas for a new feature or something along those lines, you're welcome to share them with me, but I am not likely to implement it/merge your pull without a very compelling reason. You are absolutely free to create a fork and implement the feature yourself for your and others' use. + +This, of course, does not speak for bugs. If you have a bug, please bring it to my attention, and I will try and fix it. Note that 93.7% of people's issues are caused by incorrect markup, so please double check that first. + +## Me + +Follow me on Twitter: [@CWSpear](https://twitter.com/CWSpear) or check out my [blog](http://cameronspear.com/blog/). diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.js b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.js new file mode 100644 index 0000000..5cf5b82 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.js @@ -0,0 +1,97 @@ +/* + * Project: Bootstrap Hover Dropdown + * Author: Cameron Spear + * Contributors: Mattia Larentis + * + * Dependencies: Bootstrap's Dropdown plugin, jQuery + * + * A simple plugin to enable Bootstrap dropdowns to active on hover and provide a nice user experience. + * + * License: MIT + * + * http://cameronspear.com/blog/bootstrap-dropdown-on-hover-plugin/ + */ +;(function($, window, undefined) { + // don't do anything if touch is supported + // (plugin causes some issues on mobile) + if('ontouchstart' in document) return; + + // outside the scope of the jQuery plugin to + // keep track of all dropdowns + var $allDropdowns = $(); + + // if instantlyCloseOthers is true, then it will instantly + // shut other nav items when a new one is hovered over + $.fn.dropdownHover = function(options) { + + // the element we really care about + // is the dropdown-toggle's parent + $allDropdowns = $allDropdowns.add(this.parent()); + + return this.each(function() { + var $this = $(this), + $parent = $this.parent(), + defaults = { + delay: 500, + instantlyCloseOthers: true + }, + data = { + delay: $(this).data('delay'), + instantlyCloseOthers: $(this).data('close-others') + }, + settings = $.extend(true, {}, defaults, options, data), + timeout; + + $parent.hover(function(event) { + // so a neighbor can't open the dropdown + if(!$parent.hasClass('open') && !$this.is(event.target)) { + return true; + } + + if(settings.instantlyCloseOthers === true) + $allDropdowns.removeClass('open'); + + window.clearTimeout(timeout); + $parent.addClass('open'); + $parent.trigger($.Event('show.bs.dropdown')); + }, function() { + timeout = window.setTimeout(function() { + $parent.removeClass('open'); + $parent.trigger('hide.bs.dropdown'); + }, settings.delay); + }); + + // this helps with button groups! + $this.hover(function() { + if(settings.instantlyCloseOthers === true) + $allDropdowns.removeClass('open'); + + window.clearTimeout(timeout); + $parent.addClass('open'); + $parent.trigger($.Event('show.bs.dropdown')); + }); + + // handle submenus + $parent.find('.dropdown-submenu').each(function(){ + var $this = $(this); + var subTimeout; + $this.hover(function() { + window.clearTimeout(subTimeout); + $this.children('.dropdown-menu').show(); + // always close submenu siblings instantly + $this.siblings().children('.dropdown-menu').hide(); + }, function() { + var $submenu = $this.children('.dropdown-menu'); + subTimeout = window.setTimeout(function() { + $submenu.hide(); + }, settings.delay); + }); + }); + }); + }; + + $(document).ready(function() { + // apply dropdownHover to all elements with the data-hover="dropdown" attribute + $('[data-hover="dropdown"]').dropdownHover(); + }); +})(jQuery, this); diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.min.js b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.min.js new file mode 100644 index 0000000..389562d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bootstrap-hover-dropdown.min.js @@ -0,0 +1,13 @@ +/* + * Project: Bootstrap Hover Dropdown + * Author: Cameron Spear + * Contributors: Mattia Larentis + * + * Dependencies: Bootstrap's Dropdown plugin, jQuery + * + * A simple plugin to enable Bootstrap dropdowns to active on hover and provide a nice user experience. + * + * License: MIT + * + * http://cameronspear.com/blog/bootstrap-dropdown-on-hover-plugin/ + */(function(e,t,n){if("ontouchstart"in document)return;var r=e();e.fn.dropdownHover=function(n){r=r.add(this.parent());return this.each(function(){var i=e(this),s=i.parent(),o={delay:500,instantlyCloseOthers:!0},u={delay:e(this).data("delay"),instantlyCloseOthers:e(this).data("close-others")},a=e.extend(!0,{},o,n,u),f;s.hover(function(n){if(!s.hasClass("open")&&!i.is(n.target))return!0;a.instantlyCloseOthers===!0&&r.removeClass("open");t.clearTimeout(f);s.addClass("open");s.trigger(e.Event("show.bs.dropdown"))},function(){f=t.setTimeout(function(){s.removeClass("open");s.trigger("hide.bs.dropdown")},a.delay)});i.hover(function(){a.instantlyCloseOthers===!0&&r.removeClass("open");t.clearTimeout(f);s.addClass("open");s.trigger(e.Event("show.bs.dropdown"))});s.find(".dropdown-submenu").each(function(){var n=e(this),r;n.hover(function(){t.clearTimeout(r);n.children(".dropdown-menu").show();n.siblings().children(".dropdown-menu").hide()},function(){var e=n.children(".dropdown-menu");r=t.setTimeout(function(){e.hide()},a.delay)})})})};e(document).ready(function(){e('[data-hover="dropdown"]').dropdownHover()})})(jQuery,this); \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bower.json b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bower.json new file mode 100644 index 0000000..daab6d8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/bower.json @@ -0,0 +1,20 @@ +{ + "name": "bootstrap-hover-dropdown", + "version": "2.0.1", + "description": "An unofficial Bootstrap plugin to enable Bootstrap dropdowns to activate on hover and provide a nice user experience.", + "main": "./bootstrap-hover-dropdown.js", + "keywords": [ + "twitter", + "bootstrap", + "hover", + "dropdowns" + ], + "homepage": "https://github.com/CWSpear/bootstrap-hover-dropdown", + "dependencies": { + "bootstrap": "~3.0.0" + }, + "author": { + "name": "Cameron Spear", + "web": "http://cameronspear.com" + } +} diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/demo.html b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/demo.html new file mode 100644 index 0000000..f658ae8 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap-hover-dropdown/demo.html @@ -0,0 +1,297 @@ + + + + + Bootstrap: Dropdown on Hover Plugin Demo + + + + + + + + + + + + + +
+
+
+ Please note that Bootstrap 3 does not support submenus. See this comment from one of the original authors. +
+ +

Bootstrap: Dropdown on Hover Plugin Demo

+

Hover over the nav items to see that they activate on hover. I'm setting the instantlyCloseOthers flag to true, so when you hover over a new nav item, the other ones instantly close (instead of waiting for their timeouts). See cameronspear.com/blog/bootstrap-dropdown-on-hover-plugin/ for more.

+ +

New! Try it with data-attributes

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

It also works with buttons and tabs

+

Just add data-hover="dropdown" where you'd put data-toggle="dropdown"

+ + + +

Works with button groups

+

This was trickier than you might think!

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

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

+
+
+

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

+
+ + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.css b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.css new file mode 100644 index 0000000..7f36651 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.css @@ -0,0 +1,5785 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + select { + background: #fff !important; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 62.5%; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #428bca; + text-decoration: none; +} +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #999; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 200; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +cite { + font-style: normal; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-muted { + color: #999; +} +.text-primary { + color: #428bca; +} +a.text-primary:hover { + color: #3071a9; +} +.text-success { + color: #3c763d; +} +a.text-success:hover { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #428bca; +} +a.bg-primary:hover { + background-color: #3071a9; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #999; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +blockquote:before, +blockquote:after { + content: ""; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + white-space: nowrap; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: 0; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: 0; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: 0; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: 0; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: 0; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: 0; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: 0; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: 0; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + max-width: 100%; + background-color: transparent; +} +th { + text-align: left; +} +.table { + width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +@media (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: scroll; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + /* IE8-9 */ + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +input[type="date"] { + line-height: 34px; +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + display: block; + min-height: 20px; + padding-left: 20px; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + display: inline; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.has-feedback .form-control-feedback { + position: absolute; + top: 25px; + right: 0; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.form-control-static { + margin-bottom: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .control-label, +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +.form-horizontal .form-control-static { + padding-top: 7px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + top: 0; + right: 15px; +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + color: #333; + background-color: #ebebeb; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #428bca; + border-color: #357ebd; +} +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + color: #fff; + background-color: #3276b1; + border-color: #285e8e; +} +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} +.btn-primary .badge { + color: #428bca; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + color: #fff; + background-color: #47a447; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + color: #fff; + background-color: #39b3d7; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ed9c28; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + color: #fff; + background-color: #d2322d; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #428bca; + cursor: pointer; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height .35s ease; + transition: height .35s ease; +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #999; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px solid; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus { + outline: none; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #999; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #999; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #428bca; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #428bca; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + max-height: 340px; + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: none; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } + .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-form.navbar-right:last-child { + margin-right: -15px; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } + .navbar-text.navbar-right:last-child { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #999; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #999; +} +.navbar-inverse .navbar-nav > li > a { + color: #999; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #999; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #999; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #999; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #428bca; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + color: #2a6496; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #428bca; + border-color: #428bca; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #999; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +.label[href]:hover, +.label[href]:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #999; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #808080; +} +.label-primary { + background-color: #428bca; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #999; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #428bca; + background-color: #fff; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.container .jumbotron { + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #428bca; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable { + padding-right: 35px; +} +.alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media-object { + display: block; +} +.media-heading { + margin: 0 0 5px; +} +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-right { + margin-left: 10px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +a.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + background-color: #f5f5f5; +} +a.list-group-item.active, +a.list-group-item.active:hover, +a.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #428bca; + border-color: #428bca; +} +a.list-group-item.active .list-group-item-heading, +a.list-group-item.active:hover .list-group-item-heading, +a.list-group-item.active:focus .list-group-item-heading { + color: inherit; +} +a.list-group-item.active .list-group-item-text, +a.list-group-item.active:hover .list-group-item-text, +a.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table { + margin-bottom: 0; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + overflow: hidden; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #428bca; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #428bca; + border-color: #428bca; +} +.panel-primary > .panel-heading + .panel-collapse .panel-body { + border-top-color: #428bca; +} +.panel-primary > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #428bca; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #ebccd1; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: auto; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -moz-transition: -moz-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + min-height: 16.42857143px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 20px; +} +.modal-footer { + padding: 19px 20px 20px; + margin-top: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 12px; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, .5) 0%), color-stop(rgba(0, 0, 0, .0001) 100%)); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, .0001) 0%), color-stop(rgba(0, 0, 0, .5) 100%)); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: none; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.css.map b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.css.map new file mode 100644 index 0000000..6bc5a2d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/normalize.less","less/print.less","less/scaffolding.less","less/mixins.less","less/variables.less","less/thumbnails.less","less/carousel.less","less/type.less","less/code.less","less/grid.less","less/tables.less","less/forms.less","less/buttons.less","less/button-groups.less","less/component-animations.less","less/glyphicons.less","less/dropdowns.less","less/input-groups.less","less/navs.less","less/navbar.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/pager.less","less/labels.less","less/badges.less","less/jumbotron.less","less/alerts.less","less/progress-bars.less","less/media.less","less/list-group.less","less/panels.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/popovers.less","less/responsive-utilities.less"],"names":[],"mappings":";AAQA;EACE,uBAAA;EACA,0BAAA;EACA,8BAAA;;AAOF;EACE,SAAA;;AAUF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;EACE,cAAA;;AAQF;AACA;AACA;AACA;EACE,qBAAA;EACA,wBAAA;;AAQF,KAAK,IAAI;EACP,aAAA;EACA,SAAA;;AAQF;AACA;EACE,aAAA;;AAUF;EACE,uBAAA;;AAOF,CAAC;AACD,CAAC;EACC,UAAA;;AAUF,IAAI;EACF,yBAAA;;AAOF;AACA;EACE,iBAAA;;AAOF;EACE,kBAAA;;AAQF;EACE,cAAA;EACA,gBAAA;;AAOF;EACE,gBAAA;EACA,WAAA;;AAOF;EACE,cAAA;;AAOF;AACA;EACE,cAAA;EACA,cAAA;EACA,kBAAA;EACA,wBAAA;;AAGF;EACE,WAAA;;AAGF;EACE,eAAA;;AAUF;EACE,SAAA;;AAOF,GAAG,IAAI;EACL,gBAAA;;AAUF;EACE,gBAAA;;AAOF;EACE,4BAAA;EACA,uBAAA;EACA,SAAA;;AAOF;EACE,cAAA;;AAOF;AACA;AACA;AACA;EACE,iCAAA;EACA,cAAA;;AAkBF;AACA;AACA;AACA;AACA;EACE,cAAA;EACA,aAAA;EACA,SAAA;;AAOF;EACE,iBAAA;;AAUF;AACA;EACE,oBAAA;;AAWF;AACA,IAAK,MAAK;AACV,KAAK;AACL,KAAK;EACH,0BAAA;EACA,eAAA;;AAOF,MAAM;AACN,IAAK,MAAK;EACR,eAAA;;AAOF,MAAM;AACN,KAAK;EACH,SAAA;EACA,UAAA;;AAQF;EACE,mBAAA;;AAWF,KAAK;AACL,KAAK;EACH,sBAAA;EACA,UAAA;;AASF,KAAK,eAAe;AACpB,KAAK,eAAe;EAClB,YAAA;;AASF,KAAK;EACH,6BAAA;EACA,4BAAA;EACA,+BAAA;EACA,uBAAA;;AASF,KAAK,eAAe;AACpB,KAAK,eAAe;EAClB,wBAAA;;AAOF;EACE,yBAAA;EACA,aAAA;EACA,8BAAA;;AAQF;EACE,SAAA;EACA,UAAA;;AAOF;EACE,cAAA;;AAQF;EACE,iBAAA;;AAUF;EACE,yBAAA;EACA,iBAAA;;AAGF;AACA;EACE,UAAA;;AChUF;EA9FE;IACE,4BAAA;IACA,sBAAA;IACA,kCAAA;IACA,2BAAA;;EAGF;EACA,CAAC;IACC,0BAAA;;EAGF,CAAC,MAAM;IACL,SAAS,KAAK,WAAW,GAAzB;;EAGF,IAAI,OAAO;IACT,SAAS,KAAK,YAAY,GAA1B;;EAIF,CAAC,qBAAqB;EACtB,CAAC,WAAW;IACV,SAAS,EAAT;;EAGF;EACA;IACE,sBAAA;IACA,wBAAA;;EAGF;IACE,2BAAA;;EAGF;EACA;IACE,wBAAA;;EAGF;IACE,0BAAA;;EAGF;EACA;EACA;IACE,UAAA;IACA,SAAA;;EAGF;EACA;IACE,uBAAA;;EAKF;IACE,2BAAA;;EAIF;IACE,aAAA;;EAEF,MACE;EADF,MAEE;IACE,iCAAA;;EAGJ,IAEE;EADF,OAAQ,OACN;IACE,iCAAA;;EAGJ;IACE,sBAAA;;EAGF;IACE,oCAAA;;EAEF,eACE;EADF,eAEE;IACE,iCAAA;;;ACtFN;ECyOE,8BAAA;EACG,2BAAA;EACK,sBAAA;;ADxOV,CAAC;AACD,CAAC;ECqOC,8BAAA;EACG,2BAAA;EACK,sBAAA;;ADhOV;EACE,gBAAA;EACA,6CAAA;;AAGF;EACE,aEcwB,8CFdxB;EACA,eAAA;EACA,uBAAA;EACA,cAAA;EACA,yBAAA;;AAIF;AACA;AACA;AACA;EACE,oBAAA;EACA,kBAAA;EACA,oBAAA;;AAMF;EACE,cAAA;EACA,qBAAA;;AAEA,CAAC;AACD,CAAC;EACC,cAAA;EACA,0BAAA;;AAGF,CAAC;ECzBD,oBAAA;EAEA,0CAAA;EACA,oBAAA;;ADiCF;EACE,SAAA;;AAMF;EACE,sBAAA;;AAIF;AG1EA,UAUE;AAVF,UAWE,EAAE;ACPJ,eAKE,QAME;AAXJ,eAKE,QAOE,IAAI;EHyWN,cAAA;EACA,eAAA;EACA,YAAA;;AD5SF;EACE,kBAAA;;AAMF;EACE,YAAA;EACA,uBAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;EC8BA,wCAAA;EACQ,gCAAA;EA+PR,qBAAA;EACA,eAAA;EACA,YAAA;;ADxRF;EACE,kBAAA;;AAMF;EACE,gBAAA;EACA,mBAAA;EACA,SAAA;EACA,6BAAA;;AAQF;EACE,kBAAA;EACA,UAAA;EACA,WAAA;EACA,YAAA;EACA,UAAA;EACA,gBAAA;EACA,MAAM,gBAAN;EACA,SAAA;;AK5HF;AAAI;AAAI;AAAI;AAAI;AAAI;AACpB;AAAK;AAAK;AAAK;AAAK;AAAK;EACvB,oBAAA;EACA,gBAAA;EACA,gBAAA;EACA,cAAA;;AALF,EAOE;AAPE,EAOF;AAPM,EAON;AAPU,EAOV;AAPc,EAOd;AAPkB,EAOlB;AANF,GAME;AANG,GAMH;AANQ,GAMR;AANa,GAMb;AANkB,GAMlB;AANuB,GAMvB;AAPF,EAQE;AARE,EAQF;AARM,EAQN;AARU,EAQV;AARc,EAQd;AARkB,EAQlB;AAPF,GAOE;AAPG,GAOH;AAPQ,GAOR;AAPa,GAOb;AAPkB,GAOlB;AAPuB,GAOvB;EACE,mBAAA;EACA,cAAA;EACA,cAAA;;AAIJ;AAAI;AACJ;AAAI;AACJ;AAAI;EACF,gBAAA;EACA,mBAAA;;AAJF,EAME;AANE,GAMF;AALF,EAKE;AALE,GAKF;AAJF,EAIE;AAJE,GAIF;AANF,EAOE;AAPE,GAOF;AANF,EAME;AANE,GAMF;AALF,EAKE;AALE,GAKF;EACE,cAAA;;AAGJ;AAAI;AACJ;AAAI;AACJ;AAAI;EACF,gBAAA;EACA,mBAAA;;AAJF,EAME;AANE,GAMF;AALF,EAKE;AALE,GAKF;AAJF,EAIE;AAJE,GAIF;AANF,EAOE;AAPE,GAOF;AANF,EAME;AANE,GAMF;AALF,EAKE;AALE,GAKF;EACE,cAAA;;AAIJ;AAAI;EAAM,eAAA;;AACV;AAAI;EAAM,eAAA;;AACV;AAAI;EAAM,eAAA;;AACV;AAAI;EAAM,eAAA;;AACV;AAAI;EAAM,eAAA;;AACV;AAAI;EAAM,eAAA;;AAMV;EACE,gBAAA;;AAGF;EACE,mBAAA;EACA,eAAA;EACA,gBAAA;EACA,gBAAA;;AAKF,QAHqC;EAGrC;IAFI,eAAA;;;AASJ;AACA;EAAU,cAAA;;AAGV;EAAU,kBAAA;;AAGV;EAAuB,gBAAA;;AACvB;EAAuB,iBAAA;;AACvB;EAAuB,kBAAA;;AACvB;EAAuB,mBAAA;;AAGvB;EACE,cAAA;;AAEF;EJofE,cAAA;;AACA,CAAC,aAAC;EACA,cAAA;;AInfJ;EJifE,cAAA;;AACA,CAAC,aAAC;EACA,cAAA;;AIhfJ;EJ8eE,cAAA;;AACA,CAAC,UAAC;EACA,cAAA;;AI7eJ;EJ2eE,cAAA;;AACA,CAAC,aAAC;EACA,cAAA;;AI1eJ;EJweE,cAAA;;AACA,CAAC,YAAC;EACA,cAAA;;AIneJ;EAGE,WAAA;EJqdA,yBAAA;;AACA,CAAC,WAAC;EACA,yBAAA;;AIpdJ;EJkdE,yBAAA;;AACA,CAAC,WAAC;EACA,yBAAA;;AIjdJ;EJ+cE,yBAAA;;AACA,CAAC,QAAC;EACA,yBAAA;;AI9cJ;EJ4cE,yBAAA;;AACA,CAAC,WAAC;EACA,yBAAA;;AI3cJ;EJycE,yBAAA;;AACA,CAAC,UAAC;EACA,yBAAA;;AIncJ;EACE,mBAAA;EACA,mBAAA;EACA,gCAAA;;AAQF;AACA;EACE,aAAA;EACA,mBAAA;;AAHF,EAIE;AAHF,EAGE;AAJF,EAKE;AAJF,EAIE;EACE,gBAAA;;AAOJ;EACE,eAAA;EACA,gBAAA;;AAIF;EALE,eAAA;EACA,gBAAA;EAMA,iBAAA;;AAFF,YAIE;EACE,qBAAA;EACA,iBAAA;EACA,kBAAA;;AAKJ;EACE,aAAA;EACA,mBAAA;;AAEF;AACA;EACE,uBAAA;;AAEF;EACE,iBAAA;;AAEF;EACE,cAAA;;AAwBF,QAhB2C;EACzC,cACE;IACE,WAAA;IACA,YAAA;IACA,WAAA;IACA,iBAAA;IJ1IJ,gBAAA;IACA,uBAAA;IACA,mBAAA;;EImIA,cAQE;IACE,kBAAA;;;AAUN,IAAI;AAEJ,IAAI;EACF,YAAA;EACA,iCAAA;;AAEF;EACE,cAAA;EACA,yBAAA;;AAIF;EACE,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,8BAAA;;AAKE,UAHF,EAGG;AAAD,UAFF,GAEG;AAAD,UADF,GACG;EACC,gBAAA;;AAVN,UAgBE;AAhBF,UAiBE;AAjBF,UAkBE;EACE,cAAA;EACA,cAAA;EACA,uBAAA;EACA,cAAA;;AAEA,UARF,OAQG;AAAD,UAPF,MAOG;AAAD,UANF,OAMG;EACC,SAAS,aAAT;;AAQN;AACA,UAAU;EACR,mBAAA;EACA,eAAA;EACA,+BAAA;EACA,cAAA;EACA,iBAAA;;AAME,mBAHF,OAGG;AAAD,UAXM,WAQR,OAGG;AAAD,mBAFF,MAEG;AAAD,UAXM,WASR,MAEG;AAAD,mBADF,OACG;AAAD,UAXM,WAUR,OACG;EAAU,SAAS,EAAT;;AACX,mBAJF,OAIG;AAAD,UAZM,WAQR,OAIG;AAAD,mBAHF,MAGG;AAAD,UAZM,WASR,MAGG;AAAD,mBAFF,OAEG;AAAD,UAZM,WAUR,OAEG;EACC,SAAS,aAAT;;AAMN,UAAU;AACV,UAAU;EACR,SAAS,EAAT;;AAIF;EACE,mBAAA;EACA,kBAAA;EACA,uBAAA;;AC7RF;AACA;AACA;AACA;EACE,sCJkCiD,wBIlCjD;;AAIF;EACE,gBAAA;EACA,cAAA;EACA,cAAA;EACA,yBAAA;EACA,mBAAA;EACA,kBAAA;;AAIF;EACE,gBAAA;EACA,cAAA;EACA,cAAA;EACA,yBAAA;EACA,kBAAA;EACA,8CAAA;;AAIF;EACE,cAAA;EACA,cAAA;EACA,gBAAA;EACA,eAAA;EACA,uBAAA;EACA,qBAAA;EACA,qBAAA;EACA,cAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;;AAXF,GAcE;EACE,UAAA;EACA,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,6BAAA;EACA,gBAAA;;AAKJ;EACE,iBAAA;EACA,kBAAA;;ACpDF;ENqnBE,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,mBAAA;;AMlnBA,QAHmC;EAGnC;IAFE,YAAA;;;AAKF,QAHmC;EAGnC;IAFE,YAAA;;;AAKJ,QAHqC;EAGrC;IAFI,aAAA;;;AAUJ;ENimBE,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,mBAAA;;AM3lBF;ENimBE,kBAAA;EACA,mBAAA;;AAqIE;EACE,kBAAA;EAEA,eAAA;EAEA,kBAAA;EACA,mBAAA;;AAgBF;EACE,WAAA;;AAOJ,KAAK,EAAQ,CAAC;EACZ,WAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,UAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,UAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,UAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,mBAAA;;AADF,KAAK,EAAQ,CAAC;EACZ,kBAAA;;AASF,KAAK,EAAQ,MAAM;EACjB,WAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,UAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,UAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,UAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,mBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,SAAA;;AANF,KAAK,EAAQ,MAAM;EACjB,UAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,SAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,SAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,SAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,kBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,iBAAA;;AADF,KAAK,EAAQ,MAAM;EACjB,QAAA;;AASF,KAAK,EAAQ,QAAQ;EACnB,iBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,gBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,gBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,gBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,yBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,wBAAA;;AADF,KAAK,EAAQ,QAAQ;EACnB,eAAA;;AMvvBJ,QALmC;ENouB/B;IACE,WAAA;;EAOJ,KAAK,EAAQ,CAAC;IACZ,WAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,kBAAA;;EASF,KAAK,EAAQ,MAAM;IACjB,WAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EANF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,iBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,QAAA;;EASF,KAAK,EAAQ,QAAQ;IACnB,iBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,wBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,eAAA;;;AM9uBJ,QALmC;EN2tB/B;IACE,WAAA;;EAOJ,KAAK,EAAQ,CAAC;IACZ,WAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,kBAAA;;EASF,KAAK,EAAQ,MAAM;IACjB,WAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EANF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,iBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,QAAA;;EASF,KAAK,EAAQ,QAAQ;IACnB,iBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,wBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,eAAA;;;AMvuBJ,QAHmC;ENktB/B;IACE,WAAA;;EAOJ,KAAK,EAAQ,CAAC;IACZ,WAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,UAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,mBAAA;;EADF,KAAK,EAAQ,CAAC;IACZ,kBAAA;;EASF,KAAK,EAAQ,MAAM;IACjB,WAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,mBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EANF,KAAK,EAAQ,MAAM;IACjB,UAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,SAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,kBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,iBAAA;;EADF,KAAK,EAAQ,MAAM;IACjB,QAAA;;EASF,KAAK,EAAQ,QAAQ;IACnB,iBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,gBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,yBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,wBAAA;;EADF,KAAK,EAAQ,QAAQ;IACnB,eAAA;;;AOtzBJ;EACE,eAAA;EACA,6BAAA;;AAEF;EACE,gBAAA;;AAMF;EACE,WAAA;EACA,mBAAA;;AAFF,MAIE,QAGE,KACE;AARN,MAKE,QAEE,KACE;AARN,MAME,QACE,KACE;AARN,MAIE,QAGE,KAEE;AATN,MAKE,QAEE,KAEE;AATN,MAME,QACE,KAEE;EACE,YAAA;EACA,uBAAA;EACA,mBAAA;EACA,6BAAA;;AAbR,MAkBE,QAAQ,KAAK;EACX,sBAAA;EACA,gCAAA;;AApBJ,MAuBE,UAAU,QAGR,KAAI,YACF;AA3BN,MAwBE,WAAW,QAET,KAAI,YACF;AA3BN,MAyBE,QAAO,YACL,KAAI,YACF;AA3BN,MAuBE,UAAU,QAGR,KAAI,YAEF;AA5BN,MAwBE,WAAW,QAET,KAAI,YAEF;AA5BN,MAyBE,QAAO,YACL,KAAI,YAEF;EACE,aAAA;;AA7BR,MAkCE,QAAQ;EACN,6BAAA;;AAnCJ,MAuCE;EACE,yBAAA;;AAOJ,gBACE,QAGE,KACE;AALN,gBAEE,QAEE,KACE;AALN,gBAGE,QACE,KACE;AALN,gBACE,QAGE,KAEE;AANN,gBAEE,QAEE,KAEE;AANN,gBAGE,QACE,KAEE;EACE,YAAA;;AAWR;EACE,yBAAA;;AADF,eAEE,QAGE,KACE;AANN,eAGE,QAEE,KACE;AANN,eAIE,QACE,KACE;AANN,eAEE,QAGE,KAEE;AAPN,eAGE,QAEE,KAEE;AAPN,eAIE,QACE,KAEE;EACE,yBAAA;;AARR,eAYE,QAAQ,KACN;AAbJ,eAYE,QAAQ,KAEN;EACE,wBAAA;;AAUN,cACE,QAAQ,KAAI,UAAU,KACpB;AAFJ,cACE,QAAQ,KAAI,UAAU,KAEpB;EACE,yBAAA;;AAUN,YACE,QAAQ,KAAI,MACV;AAFJ,YACE,QAAQ,KAAI,MAEV;EACE,yBAAA;;AAUN,KAAM,IAAG;EACP,gBAAA;EACA,WAAA;EACA,qBAAA;;AAKE,KAFF,GAEG;AAAD,KADF,GACG;EACC,gBAAA;EACA,WAAA;EACA,mBAAA;;AP0SJ,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AADP,MAAO,QAAQ,KACb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAIb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AACL,MALK,QAAQ,KAKZ,CAAC,MAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,MAAS;AAAX,MAHK,QAAQ,KAGZ,CAAC,MAAS;AACX,MANK,QAAQ,KAMZ,CAAC,MAAS;AAAX,MALK,QAAQ,KAKZ,CAAC,MAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,MAAS;EACT,yBAAA;;AAMJ,YAAa,QAAQ,KACnB,KAAI,CAAC,MAAQ;AADf,YAAa,QAAQ,KAEnB,KAAI,CAAC,MAAQ;AACb,YAHW,QAAQ,KAGlB,CAAC,MAAQ,MAAO;AACjB,YAJW,QAAQ,KAIlB,CAAC,MAAQ,MAAO;EACf,yBAAA;;AAlBJ,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AADP,MAAO,QAAQ,KACb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAIb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AACL,MALK,QAAQ,KAKZ,CAAC,OAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,OAAS;AAAX,MAHK,QAAQ,KAGZ,CAAC,OAAS;AACX,MANK,QAAQ,KAMZ,CAAC,OAAS;AAAX,MALK,QAAQ,KAKZ,CAAC,OAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,OAAS;EACT,yBAAA;;AAMJ,YAAa,QAAQ,KACnB,KAAI,CAAC,OAAQ;AADf,YAAa,QAAQ,KAEnB,KAAI,CAAC,OAAQ;AACb,YAHW,QAAQ,KAGlB,CAAC,OAAQ,MAAO;AACjB,YAJW,QAAQ,KAIlB,CAAC,OAAQ,MAAO;EACf,yBAAA;;AAlBJ,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AADP,MAAO,QAAQ,KACb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAIb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AACL,MALK,QAAQ,KAKZ,CAAC,IAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,IAAS;AAAX,MAHK,QAAQ,KAGZ,CAAC,IAAS;AACX,MANK,QAAQ,KAMZ,CAAC,IAAS;AAAX,MALK,QAAQ,KAKZ,CAAC,IAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,IAAS;EACT,yBAAA;;AAMJ,YAAa,QAAQ,KACnB,KAAI,CAAC,IAAQ;AADf,YAAa,QAAQ,KAEnB,KAAI,CAAC,IAAQ;AACb,YAHW,QAAQ,KAGlB,CAAC,IAAQ,MAAO;AACjB,YAJW,QAAQ,KAIlB,CAAC,IAAQ,MAAO;EACf,yBAAA;;AAlBJ,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AADP,MAAO,QAAQ,KACb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAIb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AACL,MALK,QAAQ,KAKZ,CAAC,OAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,OAAS;AAAX,MAHK,QAAQ,KAGZ,CAAC,OAAS;AACX,MANK,QAAQ,KAMZ,CAAC,OAAS;AAAX,MALK,QAAQ,KAKZ,CAAC,OAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,OAAS;EACT,yBAAA;;AAMJ,YAAa,QAAQ,KACnB,KAAI,CAAC,OAAQ;AADf,YAAa,QAAQ,KAEnB,KAAI,CAAC,OAAQ;AACb,YAHW,QAAQ,KAGlB,CAAC,OAAQ,MAAO;AACjB,YAJW,QAAQ,KAIlB,CAAC,OAAQ,MAAO;EACf,yBAAA;;AAlBJ,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AADP,MAAO,QAAQ,KACb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAIb,KAAI,CAAC;AAHP,MAAO,QAAQ,KAGb,KAAI,CAAC;AAFP,MAAO,QAAQ,KAEb,KAAI,CAAC;AACL,MALK,QAAQ,KAKZ,CAAC,MAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,MAAS;AAAX,MAHK,QAAQ,KAGZ,CAAC,MAAS;AACX,MANK,QAAQ,KAMZ,CAAC,MAAS;AAAX,MALK,QAAQ,KAKZ,CAAC,MAAS;AAAX,MAJK,QAAQ,KAIZ,CAAC,MAAS;EACT,yBAAA;;AAMJ,YAAa,QAAQ,KACnB,KAAI,CAAC,MAAQ;AADf,YAAa,QAAQ,KAEnB,KAAI,CAAC,MAAQ;AACb,YAHW,QAAQ,KAGlB,CAAC,MAAQ,MAAO;AACjB,YAJW,QAAQ,KAIlB,CAAC,MAAQ,MAAO;EACf,yBAAA;;AOpON,QA/DmC;EACjC;IACE,WAAA;IACA,mBAAA;IACA,kBAAA;IACA,kBAAA;IACA,4CAAA;IACA,yBAAA;IACA,iCAAA;;EAPF,iBAUE;IACE,gBAAA;;EAXJ,iBAUE,SAIE,QAGE,KACE;EAlBR,iBAUE,SAKE,QAEE,KACE;EAlBR,iBAUE,SAME,QACE,KACE;EAlBR,iBAUE,SAIE,QAGE,KAEE;EAnBR,iBAUE,SAKE,QAEE,KAEE;EAnBR,iBAUE,SAME,QACE,KAEE;IACE,mBAAA;;EApBV,iBA2BE;IACE,SAAA;;EA5BJ,iBA2BE,kBAIE,QAGE,KACE,KAAI;EAnCZ,iBA2BE,kBAKE,QAEE,KACE,KAAI;EAnCZ,iBA2BE,kBAME,QACE,KACE,KAAI;EAnCZ,iBA2BE,kBAIE,QAGE,KAEE,KAAI;EApCZ,iBA2BE,kBAKE,QAEE,KAEE,KAAI;EApCZ,iBA2BE,kBAME,QACE,KAEE,KAAI;IACF,cAAA;;EArCV,iBA2BE,kBAIE,QAGE,KAKE,KAAI;EAvCZ,iBA2BE,kBAKE,QAEE,KAKE,KAAI;EAvCZ,iBA2BE,kBAME,QACE,KAKE,KAAI;EAvCZ,iBA2BE,kBAIE,QAGE,KAME,KAAI;EAxCZ,iBA2BE,kBAKE,QAEE,KAME,KAAI;EAxCZ,iBA2BE,kBAME,QACE,KAME,KAAI;IACF,eAAA;;EAzCV,iBA2BE,kBAsBE,QAEE,KAAI,WACF;EApDR,iBA2BE,kBAuBE,QACE,KAAI,WACF;EApDR,iBA2BE,kBAsBE,QAEE,KAAI,WAEF;EArDR,iBA2BE,kBAuBE,QACE,KAAI,WAEF;IACE,gBAAA;;;ACxNZ;EACE,UAAA;EACA,SAAA;EACA,SAAA;EAIA,YAAA;;AAGF;EACE,cAAA;EACA,WAAA;EACA,UAAA;EACA,mBAAA;EACA,eAAA;EACA,oBAAA;EACA,cAAA;EACA,SAAA;EACA,gCAAA;;AAGF;EACE,qBAAA;EACA,kBAAA;EACA,iBAAA;;AAWF,KAAK;ERsMH,8BAAA;EACG,2BAAA;EACK,sBAAA;;AQnMV,KAAK;AACL,KAAK;EACH,eAAA;EACA,kBAAA;;EACA,mBAAA;;AAIF,KAAK;EACH,cAAA;;AAIF,KAAK;EACH,cAAA;EACA,WAAA;;AAIF,MAAM;AACN,MAAM;EACJ,YAAA;;AAIF,KAAK,aAAa;AAClB,KAAK,cAAc;AACnB,KAAK,iBAAiB;ER7CpB,oBAAA;EAEA,0CAAA;EACA,oBAAA;;AQ+CF;EACE,cAAA;EACA,gBAAA;EACA,eAAA;EACA,uBAAA;EACA,cAAA;;AA0BF;EACE,cAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,eAAA;EACA,uBAAA;EACA,cAAA;EACA,yBAAA;EACA,sBAAA;EACA,yBAAA;EACA,kBAAA;ERHA,wDAAA;EACQ,gDAAA;EAKR,8EAAA;EACQ,sEAAA;;AAmwBR,aAAC;EACC,qBAAA;EACA,UAAA;EA5wBF,sFAAA;EACQ,8EAAA;;AAlER,aAAC;EAA+B,cAAA;EACA,UAAA;;AAChC,aAAC;EAA+B,cAAA;;AAChC,aAAC;EAA+B,cAAA;;AQgFhC,aAAC;AACD,aAAC;AACD,QAAQ,UAAW;EACjB,mBAAA;EACA,yBAAA;EACA,UAAA;;AAIF,QAAQ;EACN,YAAA;;AAYJ,KAAK;EACH,wBAAA;;AASF,KAAK;EACH,iBAAA;;AASF;EACE,mBAAA;;AAQF;AACA;EACE,cAAA;EACA,gBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;;AANF,MAOE;AANF,SAME;EACE,eAAA;EACA,mBAAA;EACA,eAAA;;AAGJ,MAAO,MAAK;AACZ,aAAc,MAAK;AACnB,SAAU,MAAK;AACf,gBAAiB,MAAK;EACpB,WAAA;EACA,kBAAA;;AAEF,MAAO;AACP,SAAU;EACR,gBAAA;;AAIF;AACA;EACE,qBAAA;EACA,kBAAA;EACA,gBAAA;EACA,sBAAA;EACA,mBAAA;EACA,eAAA;;AAEF,aAAc;AACd,gBAAiB;EACf,aAAA;EACA,iBAAA;;AAYA,KANG,cAMF;AAAD,KALG,iBAKF;AAAD,MAAC;AAAD,aAAC;AAAD,SAAC;AAAD,gBAAC;AACD,QAAQ,UAAW,MAPhB;AAOH,QAAQ,UAAW,MANhB;AAMH,QAAQ,UAAW;AAAnB,QAAQ,UAAW;AAAnB,QAAQ,UAAW;AAAnB,QAAQ,UAAW;EACjB,mBAAA;;AAUJ;ERqpBE,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;;AAEA,MAAM;EACJ,YAAA;EACA,iBAAA;;AAGF,QAAQ;AACR,MAAM,UAAU;EACd,YAAA;;AQ9pBJ;ERipBE,YAAA;EACA,kBAAA;EACA,eAAA;EACA,iBAAA;EACA,kBAAA;;AAEA,MAAM;EACJ,YAAA;EACA,iBAAA;;AAGF,QAAQ;AACR,MAAM,UAAU;EACd,YAAA;;AQrpBJ;EAEE,kBAAA;;AAFF,aAKE;EACE,qBAAA;;AANJ,aAUE;EACE,kBAAA;EACA,SAAA;EACA,QAAA;EACA,cAAA;EACA,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;;AAKJ,YRsjBE;AQtjBF,YRujBE;AQvjBF,YRwjBE;AQxjBF,YRyjBE;AQzjBF,YR0jBE;AQ1jBF,YR2jBE;EACE,cAAA;;AQ5jBJ,YR+jBE;EACE,qBAAA;EAvuBF,wDAAA;EACQ,gDAAA;;AAwuBN,YAHF,cAGG;EACC,qBAAA;EA1uBJ,yEAAA;EACQ,iEAAA;;AQsKV,YRykBE;EACE,cAAA;EACA,qBAAA;EACA,yBAAA;;AQ5kBJ,YR+kBE;EACE,cAAA;;AQ7kBJ,YRmjBE;AQnjBF,YRojBE;AQpjBF,YRqjBE;AQrjBF,YRsjBE;AQtjBF,YRujBE;AQvjBF,YRwjBE;EACE,cAAA;;AQzjBJ,YR4jBE;EACE,qBAAA;EAvuBF,wDAAA;EACQ,gDAAA;;AAwuBN,YAHF,cAGG;EACC,qBAAA;EA1uBJ,yEAAA;EACQ,iEAAA;;AQyKV,YRskBE;EACE,cAAA;EACA,qBAAA;EACA,yBAAA;;AQzkBJ,YR4kBE;EACE,cAAA;;AQ1kBJ,URgjBE;AQhjBF,URijBE;AQjjBF,URkjBE;AQljBF,URmjBE;AQnjBF,URojBE;AQpjBF,URqjBE;EACE,cAAA;;AQtjBJ,URyjBE;EACE,qBAAA;EAvuBF,wDAAA;EACQ,gDAAA;;AAwuBN,UAHF,cAGG;EACC,qBAAA;EA1uBJ,yEAAA;EACQ,iEAAA;;AQ4KV,URmkBE;EACE,cAAA;EACA,qBAAA;EACA,yBAAA;;AQtkBJ,URykBE;EACE,cAAA;;AQhkBJ;EACE,gBAAA;;AASF;EACE,cAAA;EACA,eAAA;EACA,mBAAA;EACA,cAAA;;AAoEF,QAjDqC;EAiDrC,YA/CI;IACE,qBAAA;IACA,gBAAA;IACA,sBAAA;;EA4CN,YAxCI;IACE,qBAAA;IACA,WAAA;IACA,sBAAA;;EAqCN,YAlCI,aAAa;IACX,WAAA;;EAiCN,YA9BI;IACE,gBAAA;IACA,sBAAA;;EA4BN,YAtBI;EAsBJ,YArBI;IACE,qBAAA;IACA,aAAA;IACA,gBAAA;IACA,eAAA;IACA,sBAAA;;EAgBN,YAdI,OAAO,MAAK;EAchB,YAbI,UAAU,MAAK;IACb,WAAA;IACA,cAAA;;EAWN,YAJI,cAAc;IACZ,MAAA;;;AAWN,gBAGE;AAHF,gBAIE;AAJF,gBAKE;AALF,gBAME;AANF,gBAOE;EACE,aAAA;EACA,gBAAA;EACA,gBAAA;;AAVJ,gBAcE;AAdF,gBAeE;EACE,gBAAA;;AAhBJ,gBAoBE;ERyOA,kBAAA;EACA,mBAAA;;AQ9PF,gBAwBE;EACE,gBAAA;;AAUF,QANmC;EAMnC,gBALE;IACE,iBAAA;;;AA/BN,gBAuCE,cAAc;EACZ,MAAA;EACA,WAAA;;AC3aJ;EACE,qBAAA;EACA,gBAAA;EACA,mBAAA;EACA,kBAAA;EACA,sBAAA;EACA,eAAA;EACA,sBAAA;EACA,6BAAA;EACA,mBAAA;ET0gBA,iBAAA;EACA,eAAA;EACA,uBAAA;EACA,kBAAA;EAnSA,yBAAA;EACG,sBAAA;EACC,qBAAA;EACI,iBAAA;;AStON,IAAC;AAAD,IAFD,OAEE;AAAD,IADD,OACE;ETQH,oBAAA;EAEA,0CAAA;EACA,oBAAA;;ASNA,IAAC;AACD,IAAC;EACC,cAAA;EACA,qBAAA;;AAGF,IAAC;AACD,IAAC;EACC,UAAA;EACA,sBAAA;ETmFF,wDAAA;EACQ,gDAAA;;AShFR,IAAC;AACD,IAAC;AACD,QAAQ,UAAW;EACjB,mBAAA;EACA,oBAAA;ET+OF,aAAA;EAGA,yBAAA;EAvKA,wBAAA;EACQ,gBAAA;;ASlEV;ET2bE,cAAA;EACA,yBAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;AACD,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,cAAA;EACA,yBAAA;EACI,qBAAA;;AAEN,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,sBAAA;;AAKA,YAHD;AAGC,YAFD;AAEC,QADM,UAAW;AAEjB,YAJD,SAIE;AAAD,YAHD,UAGE;AAAD,QAFM,UAAW,aAEhB;AACD,YALD,SAKE;AAAD,YAJD,UAIE;AAAD,QAHM,UAAW,aAGhB;AACD,YAND,SAME;AAAD,YALD,UAKE;AAAD,QAJM,UAAW,aAIhB;AACD,YAPD,SAOE;AAAD,YAND,UAME;AAAD,QALM,UAAW,aAKhB;EACC,yBAAA;EACI,qBAAA;;AStdV,YT0dE;EACE,cAAA;EACA,yBAAA;;ASzdJ;ETwbE,cAAA;EACA,yBAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;AACD,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,cAAA;EACA,yBAAA;EACI,qBAAA;;AAEN,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,sBAAA;;AAKA,YAHD;AAGC,YAFD;AAEC,QADM,UAAW;AAEjB,YAJD,SAIE;AAAD,YAHD,UAGE;AAAD,QAFM,UAAW,aAEhB;AACD,YALD,SAKE;AAAD,YAJD,UAIE;AAAD,QAHM,UAAW,aAGhB;AACD,YAND,SAME;AAAD,YALD,UAKE;AAAD,QAJM,UAAW,aAIhB;AACD,YAPD,SAOE;AAAD,YAND,UAME;AAAD,QALM,UAAW,aAKhB;EACC,yBAAA;EACI,qBAAA;;ASndV,YTudE;EACE,cAAA;EACA,yBAAA;;ASrdJ;ETobE,cAAA;EACA,yBAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;AACD,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,cAAA;EACA,yBAAA;EACI,qBAAA;;AAEN,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,sBAAA;;AAKA,YAHD;AAGC,YAFD;AAEC,QADM,UAAW;AAEjB,YAJD,SAIE;AAAD,YAHD,UAGE;AAAD,QAFM,UAAW,aAEhB;AACD,YALD,SAKE;AAAD,YAJD,UAIE;AAAD,QAHM,UAAW,aAGhB;AACD,YAND,SAME;AAAD,YALD,UAKE;AAAD,QAJM,UAAW,aAIhB;AACD,YAPD,SAOE;AAAD,YAND,UAME;AAAD,QALM,UAAW,aAKhB;EACC,yBAAA;EACI,qBAAA;;AS/cV,YTmdE;EACE,cAAA;EACA,yBAAA;;ASjdJ;ETgbE,cAAA;EACA,yBAAA;EACA,qBAAA;;AAEA,SAAC;AACD,SAAC;AACD,SAAC;AACD,SAAC;AACD,KAAM,iBAAgB;EACpB,cAAA;EACA,yBAAA;EACI,qBAAA;;AAEN,SAAC;AACD,SAAC;AACD,KAAM,iBAAgB;EACpB,sBAAA;;AAKA,SAHD;AAGC,SAFD;AAEC,QADM,UAAW;AAEjB,SAJD,SAIE;AAAD,SAHD,UAGE;AAAD,QAFM,UAAW,UAEhB;AACD,SALD,SAKE;AAAD,SAJD,UAIE;AAAD,QAHM,UAAW,UAGhB;AACD,SAND,SAME;AAAD,SALD,UAKE;AAAD,QAJM,UAAW,UAIhB;AACD,SAPD,SAOE;AAAD,SAND,UAME;AAAD,QALM,UAAW,UAKhB;EACC,yBAAA;EACI,qBAAA;;AS3cV,ST+cE;EACE,cAAA;EACA,yBAAA;;AS7cJ;ET4aE,cAAA;EACA,yBAAA;EACA,qBAAA;;AAEA,YAAC;AACD,YAAC;AACD,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,cAAA;EACA,yBAAA;EACI,qBAAA;;AAEN,YAAC;AACD,YAAC;AACD,KAAM,iBAAgB;EACpB,sBAAA;;AAKA,YAHD;AAGC,YAFD;AAEC,QADM,UAAW;AAEjB,YAJD,SAIE;AAAD,YAHD,UAGE;AAAD,QAFM,UAAW,aAEhB;AACD,YALD,SAKE;AAAD,YAJD,UAIE;AAAD,QAHM,UAAW,aAGhB;AACD,YAND,SAME;AAAD,YALD,UAKE;AAAD,QAJM,UAAW,aAIhB;AACD,YAPD,SAOE;AAAD,YAND,UAME;AAAD,QALM,UAAW,aAKhB;EACC,yBAAA;EACI,qBAAA;;ASvcV,YT2cE;EACE,cAAA;EACA,yBAAA;;ASzcJ;ETwaE,cAAA;EACA,yBAAA;EACA,qBAAA;;AAEA,WAAC;AACD,WAAC;AACD,WAAC;AACD,WAAC;AACD,KAAM,iBAAgB;EACpB,cAAA;EACA,yBAAA;EACI,qBAAA;;AAEN,WAAC;AACD,WAAC;AACD,KAAM,iBAAgB;EACpB,sBAAA;;AAKA,WAHD;AAGC,WAFD;AAEC,QADM,UAAW;AAEjB,WAJD,SAIE;AAAD,WAHD,UAGE;AAAD,QAFM,UAAW,YAEhB;AACD,WALD,SAKE;AAAD,WAJD,UAIE;AAAD,QAHM,UAAW,YAGhB;AACD,WAND,SAME;AAAD,WALD,UAKE;AAAD,QAJM,UAAW,YAIhB;AACD,WAPD,SAOE;AAAD,WAND,UAME;AAAD,QALM,UAAW,YAKhB;EACC,yBAAA;EACI,qBAAA;;ASncV,WTucE;EACE,cAAA;EACA,yBAAA;;AShcJ;EACE,cAAA;EACA,mBAAA;EACA,eAAA;EACA,gBAAA;;AAEA;AACA,SAAC;AACD,SAAC;AACD,QAAQ,UAAW;EACjB,6BAAA;ET2BF,wBAAA;EACQ,gBAAA;;ASzBR;AACA,SAAC;AACD,SAAC;AACD,SAAC;EACC,yBAAA;;AAEF,SAAC;AACD,SAAC;EACC,cAAA;EACA,0BAAA;EACA,6BAAA;;AAIA,SAFD,UAEE;AAAD,QADM,UAAW,UAChB;AACD,SAHD,UAGE;AAAD,QAFM,UAAW,UAEhB;EACC,cAAA;EACA,qBAAA;;AASN;ACvBA,aAAc;EVubZ,kBAAA;EACA,eAAA;EACA,iBAAA;EACA,kBAAA;;AS/ZF;AC5BA,aAAc;EVwbZ,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;;AS3ZF;ACjCA,aAAc;EVybZ,gBAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;;ASnZF;EACE,cAAA;EACA,WAAA;EACA,eAAA;EACA,gBAAA;;AAIF,UAAW;EACT,eAAA;;AAOA,KAHG,eAGF;AAAD,KAFG,cAEF;AAAD,KADG,eACF;EACC,WAAA;;AEnJJ;EACE,UAAA;EXqHA,wCAAA;EACQ,gCAAA;;AWpHR,KAAC;EACC,UAAA;;AAIJ;EACE,aAAA;;AACA,SAAC;EACC,cAAA;;AAGJ;EACE,kBAAA;EACA,SAAA;EACA,gBAAA;EXqGA,qCAAA;EACQ,6BAAA;;AYtHV;EACE,aAAa,sBAAb;EACA,qDAAA;EACA,2TAAA;;AAOF;EACE,kBAAA;EACA,QAAA;EACA,qBAAA;EACA,aAAa,sBAAb;EACA,kBAAA;EACA,mBAAA;EACA,cAAA;EACA,mCAAA;EACA,kCAAA;;AAIkC,mBAAC;EAAU,SAAS,KAAT;;AACX,eAAC;EAAU,SAAS,KAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,aAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,aAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,cAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,cAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,cAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,yBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,2BAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,0BAAC;EAAU,SAAS,OAAT;;AACX,4BAAC;EAAU,SAAS,OAAT;;AACX,cAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,6BAAC;EAAU,SAAS,OAAT;;AACX,4BAAC;EAAU,SAAS,OAAT;;AACX,0BAAC;EAAU,SAAS,OAAT;;AACX,4BAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,cAAC;EAAU,SAAS,OAAT;;AACX,cAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,2BAAC;EAAU,SAAS,OAAT;;AACX,+BAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,4BAAC;EAAU,SAAS,OAAT;;AACX,6BAAC;EAAU,SAAS,OAAT;;AACX,iCAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,eAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,wBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,kBAAC;EAAU,SAAS,OAAT;;AACX,iBAAC;EAAU,SAAS,OAAT;;AACX,qBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,gBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,mBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,sBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,oBAAC;EAAU,SAAS,OAAT;;AACX,yBAAC;EAAU,SAAS,OAAT;;AACX,4BAAC;EAAU,SAAS,OAAT;;AACX,yBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,uBAAC;EAAU,SAAS,OAAT;;AACX,yBAAC;EAAU,SAAS,OAAT;;AClO/C;EACE,qBAAA;EACA,QAAA;EACA,SAAA;EACA,gBAAA;EACA,sBAAA;EACA,qBAAA;EACA,mCAAA;EACA,kCAAA;;AAIF;EACE,kBAAA;;AAIF,gBAAgB;EACd,UAAA;;AAIF;EACE,kBAAA;EACA,SAAA;EACA,OAAA;EACA,aAAA;EACA,aAAA;EACA,WAAA;EACA,gBAAA;EACA,cAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;EACA,yBAAA;EACA,yBAAA;EACA,qCAAA;EACA,kBAAA;Eb8EA,mDAAA;EACQ,2CAAA;Ea7ER,4BAAA;;AAKA,cAAC;EACC,QAAA;EACA,UAAA;;AAxBJ,cA4BE;EboVA,WAAA;EACA,aAAA;EACA,gBAAA;EACA,yBAAA;;AanXF,cAiCE,KAAK;EACH,cAAA;EACA,iBAAA;EACA,WAAA;EACA,mBAAA;EACA,uBAAA;EACA,cAAA;EACA,mBAAA;;AAMF,cADa,KAAK,IACjB;AACD,cAFa,KAAK,IAEjB;EACC,qBAAA;EACA,cAAA;EACA,yBAAA;;AAMF,cADa,UAAU;AAEvB,cAFa,UAAU,IAEtB;AACD,cAHa,UAAU,IAGtB;EACC,cAAA;EACA,qBAAA;EACA,UAAA;EACA,yBAAA;;AASF,cADa,YAAY;AAEzB,cAFa,YAAY,IAExB;AACD,cAHa,YAAY,IAGxB;EACC,cAAA;;AAKF,cADa,YAAY,IACxB;AACD,cAFa,YAAY,IAExB;EACC,qBAAA;EACA,6BAAA;EACA,sBAAA;EbkPF,mEAAA;EahPE,mBAAA;;AAKJ,KAEE;EACE,cAAA;;AAHJ,KAOE;EACE,UAAA;;AAQJ;EACE,UAAA;EACA,QAAA;;AAQF;EACE,OAAA;EACA,WAAA;;AAIF;EACE,cAAA;EACA,iBAAA;EACA,eAAA;EACA,uBAAA;EACA,cAAA;;AAIF;EACE,eAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA,MAAA;EACA,YAAA;;AAIF,WAAY;EACV,QAAA;EACA,UAAA;;AAQF,OAGE;AAFF,oBAAqB,UAEnB;EACE,aAAA;EACA,wBAAA;EACA,SAAS,EAAT;;AANJ,OASE;AARF,oBAAqB,UAQnB;EACE,SAAA;EACA,YAAA;EACA,kBAAA;;AAsBJ,QAb2C;EACzC,aACE;IAnEF,UAAA;IACA,QAAA;;EAiEA,aAME;IA9DF,OAAA;IACA,WAAA;;;AH7IF;AACA;EACE,kBAAA;EACA,qBAAA;EACA,sBAAA;;AAJF,UAKE;AAJF,mBAIE;EACE,kBAAA;EACA,WAAA;;AAEA,UAJF,OAIG;AAAD,mBAJF,OAIG;AACD,UALF,OAKG;AAAD,mBALF,OAKG;AACD,UANF,OAMG;AAAD,mBANF,OAMG;AACD,UAPF,OAOG;AAAD,mBAPF,OAOG;EACC,UAAA;;AAEF,UAVF,OAUG;AAAD,mBAVF,OAUG;EAEC,aAAA;;AAMN,UACE,KAAK;AADP,UAEE,KAAK;AAFP,UAGE,WAAW;AAHb,UAIE,WAAW;EACT,iBAAA;;AAKJ;EACE,iBAAA;;AADF,YAIE;AAJF,YAKE;EACE,WAAA;;AANJ,YAQE;AARF,YASE;AATF,YAUE;EACE,gBAAA;;AAIJ,UAAW,OAAM,IAAI,cAAc,IAAI,aAAa,IAAI;EACtD,gBAAA;;AAIF,UAAW,OAAM;EACf,cAAA;;AACA,UAFS,OAAM,YAEd,IAAI,aAAa,IAAI;EV2CtB,6BAAA;EACG,0BAAA;;AUvCL,UAAW,OAAM,WAAW,IAAI;AAChC,UAAW,mBAAkB,IAAI;EV6C/B,4BAAA;EACG,yBAAA;;AUzCL,UAAW;EACT,WAAA;;AAEF,UAAW,aAAY,IAAI,cAAc,IAAI,aAAc;EACzD,gBAAA;;AAEF,UAAW,aAAY,YACrB,OAAM;AADR,UAAW,aAAY,YAErB;EVwBA,6BAAA;EACG,0BAAA;;AUrBL,UAAW,aAAY,WAAY,OAAM;EV4BvC,4BAAA;EACG,yBAAA;;AUxBL,UAAW,iBAAgB;AAC3B,UAAU,KAAM;EACd,UAAA;;AAiBF,UAAW,OAAO;EAChB,iBAAA;EACA,kBAAA;;AAEF,UAAW,UAAU;EACnB,kBAAA;EACA,mBAAA;;AAKF,UAAU,KAAM;EVGd,wDAAA;EACQ,gDAAA;;AUAR,UAJQ,KAAM,iBAIb;EVDD,wBAAA;EACQ,gBAAA;;AUOV,IAAK;EACH,cAAA;;AAGF,OAAQ;EACN,uBAAA;EACA,sBAAA;;AAGF,OAAQ,QAAQ;EACd,uBAAA;;AAOF,mBACE;AADF,mBAEE;AAFF,mBAGE,aAAa;EACX,cAAA;EACA,WAAA;EACA,WAAA;EACA,eAAA;;AAPJ,mBAWE,aAEE;EACE,WAAA;;AAdN,mBAkBE,OAAO;AAlBT,mBAmBE,OAAO;AAnBT,mBAoBE,aAAa;AApBf,mBAqBE,aAAa;EACX,gBAAA;EACA,cAAA;;AAKF,mBADkB,OACjB,IAAI,cAAc,IAAI;EACrB,gBAAA;;AAEF,mBAJkB,OAIjB,YAAY,IAAI;EACf,4BAAA;EVvEF,6BAAA;EACC,4BAAA;;AUyED,mBARkB,OAQjB,WAAW,IAAI;EACd,8BAAA;EVnFF,0BAAA;EACC,yBAAA;;AUsFH,mBAAoB,aAAY,IAAI,cAAc,IAAI,aAAc;EAClE,gBAAA;;AAEF,mBAAoB,aAAY,YAAY,IAAI,aAC9C,OAAM;AADR,mBAAoB,aAAY,YAAY,IAAI,aAE9C;EVpFA,6BAAA;EACC,4BAAA;;AUuFH,mBAAoB,aAAY,WAAW,IAAI,cAAe,OAAM;EVhGlE,0BAAA;EACC,yBAAA;;AUwGH;EACE,cAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;;AAJF,oBAKE;AALF,oBAME;EACE,WAAA;EACA,mBAAA;EACA,SAAA;;AATJ,oBAWE,aAAa;EACX,WAAA;;AAMJ,uBAAwB,OAAO,QAAO;AACtC,uBAAwB,OAAO,QAAO;EACpC,aAAA;;AI1NF;EACE,kBAAA;EACA,cAAA;EACA,yBAAA;;AAGA,YAAC;EACC,WAAA;EACA,eAAA;EACA,gBAAA;;AATJ,YAYE;EAGE,kBAAA;EACA,UAAA;EAKA,WAAA;EAEA,WAAA;EACA,gBAAA;;AASJ,eAAgB;AAChB,eAAgB;AAChB,eAAgB,mBAAmB;Edw2BjC,YAAA;EACA,kBAAA;EACA,eAAA;EACA,iBAAA;EACA,kBAAA;;AAEA,MAAM,ech3BQ;Adg3Bd,MAAM,ec/2BQ;Ad+2Bd,MAAM,ec92BQ,mBAAmB;Ed+2B/B,YAAA;EACA,iBAAA;;AAGF,QAAQ,ecr3BM;Adq3Bd,QAAQ,ecp3BM;Ado3Bd,QAAQ,ecn3BM,mBAAmB;Ado3BjC,MAAM,UAAU,ect3BF;Ads3Bd,MAAM,UAAU,ecr3BF;Adq3Bd,MAAM,UAAU,ecp3BF,mBAAmB;Edq3B/B,YAAA;;Acp3BJ,eAAgB;AAChB,eAAgB;AAChB,eAAgB,mBAAmB;Edq2BjC,YAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;;AAEA,MAAM,ec72BQ;Ad62Bd,MAAM,ec52BQ;Ad42Bd,MAAM,ec32BQ,mBAAmB;Ed42B/B,YAAA;EACA,iBAAA;;AAGF,QAAQ,ecl3BM;Adk3Bd,QAAQ,ecj3BM;Adi3Bd,QAAQ,ech3BM,mBAAmB;Adi3BjC,MAAM,UAAU,ecn3BF;Adm3Bd,MAAM,UAAU,ecl3BF;Adk3Bd,MAAM,UAAU,ecj3BF,mBAAmB;Edk3B/B,YAAA;;Ac72BJ;AACA;AACA,YAAa;EACX,mBAAA;;AAEA,kBAAC,IAAI,cAAc,IAAI;AAAvB,gBAAC,IAAI,cAAc,IAAI;AAAvB,YAHW,cAGV,IAAI,cAAc,IAAI;EACrB,gBAAA;;AAIJ;AACA;EACE,SAAA;EACA,mBAAA;EACA,sBAAA;;AAKF;EACE,iBAAA;EACA,eAAA;EACA,mBAAA;EACA,cAAA;EACA,cAAA;EACA,kBAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;;AAGA,kBAAC;EACC,iBAAA;EACA,eAAA;EACA,kBAAA;;AAEF,kBAAC;EACC,kBAAA;EACA,eAAA;EACA,kBAAA;;AApBJ,kBAwBE,MAAK;AAxBP,kBAyBE,MAAK;EACH,aAAA;;AAKJ,YAAa,cAAa;AAC1B,kBAAkB;AAClB,gBAAgB,YAAa;AAC7B,gBAAgB,YAAa,aAAa;AAC1C,gBAAgB,YAAa;AAC7B,gBAAgB,WAAY,OAAM,IAAI,aAAa,IAAI;AACvD,gBAAgB,WAAY,aAAY,IAAI,aAAc;EdFxD,6BAAA;EACG,0BAAA;;AcIL,kBAAkB;EAChB,eAAA;;AAEF,YAAa,cAAa;AAC1B,kBAAkB;AAClB,gBAAgB,WAAY;AAC5B,gBAAgB,WAAY,aAAa;AACzC,gBAAgB,WAAY;AAC5B,gBAAgB,YAAa,OAAM,IAAI;AACvC,gBAAgB,YAAa,aAAY,IAAI,cAAe;EdN1D,4BAAA;EACG,yBAAA;;AcQL,kBAAkB;EAChB,cAAA;;AAKF;EACE,kBAAA;EAGA,YAAA;EACA,mBAAA;;AALF,gBASE;EACE,kBAAA;;AAVJ,gBASE,OAEE;EACE,iBAAA;;AAGF,gBANF,OAMG;AACD,gBAPF,OAOG;AACD,gBARF,OAQG;EACC,UAAA;;AAKJ,gBAAC,YACC;AADF,gBAAC,YAEC;EACE,kBAAA;;AAGJ,gBAAC,WACC;AADF,gBAAC,WAEC;EACE,iBAAA;;ACtJN;EACE,gBAAA;EACA,eAAA;EACA,gBAAA;;AAHF,IAME;EACE,kBAAA;EACA,cAAA;;AARJ,IAME,KAIE;EACE,kBAAA;EACA,cAAA;EACA,kBAAA;;AACA,IARJ,KAIE,IAIG;AACD,IATJ,KAIE,IAKG;EACC,qBAAA;EACA,yBAAA;;AAKJ,IAhBF,KAgBG,SAAU;EACT,cAAA;;AAEA,IAnBJ,KAgBG,SAAU,IAGR;AACD,IApBJ,KAgBG,SAAU,IAIR;EACC,cAAA;EACA,qBAAA;EACA,6BAAA;EACA,mBAAA;;AAOJ,IADF,MAAM;AAEJ,IAFF,MAAM,IAEH;AACD,IAHF,MAAM,IAGH;EACC,yBAAA;EACA,qBAAA;;AAzCN,IAkDE;EfkVA,WAAA;EACA,aAAA;EACA,gBAAA;EACA,yBAAA;;AevYF,IAyDE,KAAK,IAAI;EACP,eAAA;;AASJ;EACE,gCAAA;;AADF,SAEE;EACE,WAAA;EAEA,mBAAA;;AALJ,SAEE,KAME;EACE,iBAAA;EACA,uBAAA;EACA,6BAAA;EACA,0BAAA;;AACA,SAXJ,KAME,IAKG;EACC,qCAAA;;AAMF,SAlBJ,KAiBG,OAAQ;AAEP,SAnBJ,KAiBG,OAAQ,IAEN;AACD,SApBJ,KAiBG,OAAQ,IAGN;EACC,cAAA;EACA,yBAAA;EACA,yBAAA;EACA,gCAAA;EACA,eAAA;;AAKN,SAAC;EAqDD,WAAA;EA8BA,gBAAA;;AAnFA,SAAC,cAuDD;EACE,WAAA;;AAxDF,SAAC,cAuDD,KAEG;EACC,kBAAA;EACA,kBAAA;;AA3DJ,SAAC,cA+DD,YAAY;EACV,SAAA;EACA,UAAA;;AAYJ,QATqC;EASrC,SA7EG,cAqEC;IACE,mBAAA;IACA,SAAA;;EAMN,SA7EG,cAqEC,KAGE;IACE,gBAAA;;;AAzEN,SAAC,cAqFD,KAAK;EAEH,eAAA;EACA,kBAAA;;AAxFF,SAAC,cA2FD,UAAU;AA3FV,SAAC,cA4FD,UAAU,IAAG;AA5Fb,SAAC,cA6FD,UAAU,IAAG;EACX,yBAAA;;AAcJ,QAXqC;EAWrC,SA5GG,cAkGC,KAAK;IACH,gCAAA;IACA,0BAAA;;EAQN,SA5GG,cAsGC,UAAU;EAMd,SA5GG,cAuGC,UAAU,IAAG;EAKjB,SA5GG,cAwGC,UAAU,IAAG;IACX,4BAAA;;;AAhGN,UACE;EACE,WAAA;;AAFJ,UACE,KAIE;EACE,kBAAA;;AANN,UACE,KAOE;EACE,gBAAA;;AAKA,UAbJ,KAYG,OAAQ;AAEP,UAdJ,KAYG,OAAQ,IAEN;AACD,UAfJ,KAYG,OAAQ,IAGN;EACC,cAAA;EACA,yBAAA;;AAQR,YACE;EACE,WAAA;;AAFJ,YACE,KAEE;EACE,eAAA;EACA,cAAA;;AAYN;EACE,WAAA;;AADF,cAGE;EACE,WAAA;;AAJJ,cAGE,KAEG;EACC,kBAAA;EACA,kBAAA;;AAPN,cAWE,YAAY;EACV,SAAA;EACA,UAAA;;AAYJ,QATqC;EASrC,cARI;IACE,mBAAA;IACA,SAAA;;EAMN,cARI,KAGE;IACE,gBAAA;;;AASR;EACE,gBAAA;;AADF,mBAGE,KAAK;EAEH,eAAA;EACA,kBAAA;;AANJ,mBASE,UAAU;AATZ,mBAUE,UAAU,IAAG;AAVf,mBAWE,UAAU,IAAG;EACX,yBAAA;;AAcJ,QAXqC;EAWrC,mBAVI,KAAK;IACH,gCAAA;IACA,0BAAA;;EAQN,mBANI,UAAU;EAMd,mBALI,UAAU,IAAG;EAKjB,mBAJI,UAAU,IAAG;IACX,4BAAA;;;AAUN,YACE;EACE,aAAA;;AAFJ,YAIE;EACE,cAAA;;AASJ,SAAU;EAER,gBAAA;Ef3IA,0BAAA;EACC,yBAAA;;AgB1FH;EACE,kBAAA;EACA,gBAAA;EACA,mBAAA;EACA,6BAAA;;AAQF,QAH6C;EAG7C;IAFI,kBAAA;;;AAgBJ,QAH6C;EAG7C;IAFI,WAAA;;;AAeJ;EACE,iBAAA;EACA,mBAAA;EACA,mBAAA;EACA,kBAAA;EACA,iCAAA;EACA,kDAAA;EAEA,iCAAA;;AAEA,gBAAC;EACC,gBAAA;;AA4BJ,QAzB6C;EAyB7C;IAxBI,WAAA;IACA,aAAA;IACA,gBAAA;;EAEA,gBAAC;IACC,yBAAA;IACA,uBAAA;IACA,iBAAA;IACA,4BAAA;;EAGF,gBAAC;IACC,mBAAA;;EAKF,iBAAkB;EAClB,kBAAmB;EACnB,oBAAqB;IACnB,eAAA;IACA,gBAAA;;;AAUN,UAEE;AADF,gBACE;AAFF,UAGE;AAFF,gBAEE;EACE,mBAAA;EACA,kBAAA;;AAMF,QAJ6C;EAI7C,UATA;EASA,gBATA;EASA,UARA;EAQA,gBARA;IAKI,eAAA;IACA,cAAA;;;AAaN;EACE,aAAA;EACA,qBAAA;;AAKF,QAH6C;EAG7C;IAFI,gBAAA;;;AAKJ;AACA;EACE,eAAA;EACA,QAAA;EACA,OAAA;EACA,aAAA;;AAMF,QAH6C;EAG7C;EAAA;IAFI,gBAAA;;;AAGJ;EACE,MAAA;EACA,qBAAA;;AAEF;EACE,SAAA;EACA,gBAAA;EACA,qBAAA;;AAMF;EACE,WAAA;EACA,kBAAA;EACA,eAAA;EACA,iBAAA;EACA,YAAA;;AAEA,aAAC;AACD,aAAC;EACC,qBAAA;;AASJ,QAN6C;EACzC,OAAQ,aAAa;EACrB,OAAQ,mBAAmB;IACzB,kBAAA;;;AAWN;EACE,kBAAA;EACA,YAAA;EACA,kBAAA;EACA,iBAAA;EhBsaA,eAAA;EACA,kBAAA;EgBraA,6BAAA;EACA,sBAAA;EACA,6BAAA;EACA,kBAAA;;AAIA,cAAC;EACC,aAAA;;AAdJ,cAkBE;EACE,cAAA;EACA,WAAA;EACA,WAAA;EACA,kBAAA;;AAtBJ,cAwBE,UAAU;EACR,eAAA;;AAMJ,QAH6C;EAG7C;IAFI,aAAA;;;AAUJ;EACE,mBAAA;;AADF,WAGE,KAAK;EACH,iBAAA;EACA,oBAAA;EACA,iBAAA;;AA2BF,QAxB+C;EAwB/C,WAtBE,MAAM;IACJ,gBAAA;IACA,WAAA;IACA,WAAA;IACA,aAAA;IACA,6BAAA;IACA,SAAA;IACA,gBAAA;;EAeJ,WAtBE,MAAM,eAQJ,KAAK;EAcT,WAtBE,MAAM,eASJ;IACE,0BAAA;;EAYN,WAtBE,MAAM,eAYJ,KAAK;IACH,iBAAA;;EACA,WAdJ,MAAM,eAYJ,KAAK,IAEF;EACD,WAfJ,MAAM,eAYJ,KAAK,IAGF;IACC,sBAAA;;;AAuBV,QAhB6C;EAgB7C;IAfI,WAAA;IACA,SAAA;;EAcJ,WAZI;IACE,WAAA;;EAWN,WAZI,KAEE;IACE,iBAAA;IACA,oBAAA;;EAIJ,WAAC,aAAa;IACZ,mBAAA;;;AAkBN,QAN2C;EACzC;ICnQA,sBAAA;;EDoQA;ICvQA,uBAAA;;;ADgRF;EACE,kBAAA;EACA,mBAAA;EACA,kBAAA;EACA,iCAAA;EACA,oCAAA;EhB3KA,4FAAA;EACQ,oFAAA;EAkeR,eAAA;EACA,kBAAA;;AQ3NF,QAjDqC;EAiDrC,YA/CI;IACE,qBAAA;IACA,gBAAA;IACA,sBAAA;;EA4CN,YAxCI;IACE,qBAAA;IACA,WAAA;IACA,sBAAA;;EAqCN,YAlCI,aAAa;IACX,WAAA;;EAiCN,YA9BI;IACE,gBAAA;IACA,sBAAA;;EA4BN,YAtBI;EAsBJ,YArBI;IACE,qBAAA;IACA,aAAA;IACA,gBAAA;IACA,eAAA;IACA,sBAAA;;EAgBN,YAdI,OAAO,MAAK;EAchB,YAbI,UAAU,MAAK;IACb,WAAA;IACA,cAAA;;EAWN,YAJI,cAAc;IACZ,MAAA;;;AQhFJ,QAHiD;EAGjD,YAJA;IAEI,kBAAA;;;AAsBN,QAd6C;EAc7C;IAbI,WAAA;IACA,SAAA;IACA,cAAA;IACA,eAAA;IACA,cAAA;IACA,iBAAA;IhBlMF,wBAAA;IACQ,gBAAA;;EgBqMN,YAAC,aAAa;IACZ,mBAAA;;;AASN,WAAY,KAAK;EACf,aAAA;EhBvOA,0BAAA;EACC,yBAAA;;AgB0OH,oBAAqB,YAAY,KAAK;EhBnOpC,6BAAA;EACC,4BAAA;;AgB2OH;EhBqQE,eAAA;EACA,kBAAA;;AgBnQA,WAAC;EhBkQD,gBAAA;EACA,mBAAA;;AgBhQA,WAAC;EhB+PD,gBAAA;EACA,mBAAA;;AgBtPF;EhBqPE,gBAAA;EACA,mBAAA;;AgBzOF,QAV6C;EAU7C;IATI,WAAA;IACA,iBAAA;IACA,kBAAA;;EAGA,YAAC,aAAa;IACZ,eAAA;;;AASN;EACE,yBAAA;EACA,qBAAA;;AAFF,eAIE;EACE,cAAA;;AACA,eAFF,cAEG;AACD,eAHF,cAGG;EACC,cAAA;EACA,6BAAA;;AATN,eAaE;EACE,cAAA;;AAdJ,eAiBE,YACE,KAAK;EACH,cAAA;;AAEA,eAJJ,YACE,KAAK,IAGF;AACD,eALJ,YACE,KAAK,IAIF;EACC,cAAA;EACA,6BAAA;;AAIF,eAXJ,YAUE,UAAU;AAER,eAZJ,YAUE,UAAU,IAEP;AACD,eAbJ,YAUE,UAAU,IAGP;EACC,cAAA;EACA,yBAAA;;AAIF,eAnBJ,YAkBE,YAAY;AAEV,eApBJ,YAkBE,YAAY,IAET;AACD,eArBJ,YAkBE,YAAY,IAGT;EACC,cAAA;EACA,6BAAA;;AAxCR,eA6CE;EACE,qBAAA;;AACA,eAFF,eAEG;AACD,eAHF,eAGG;EACC,yBAAA;;AAjDN,eA6CE,eAME;EACE,yBAAA;;AApDN,eAwDE;AAxDF,eAyDE;EACE,qBAAA;;AAOE,eAHJ,YAEE,QAAQ;AAEN,eAJJ,YAEE,QAAQ,IAEL;AACD,eALJ,YAEE,QAAQ,IAGL;EACC,yBAAA;EACA,cAAA;;AAiCN,QA7BiD;EA6BjD,eAxCA,YAaI,MAAM,eACJ,KAAK;IACH,cAAA;;EACA,eAhBR,YAaI,MAAM,eACJ,KAAK,IAEF;EACD,eAjBR,YAaI,MAAM,eACJ,KAAK,IAGF;IACC,cAAA;IACA,6BAAA;;EAIF,eAvBR,YAaI,MAAM,eASJ,UAAU;EAER,eAxBR,YAaI,MAAM,eASJ,UAAU,IAEP;EACD,eAzBR,YAaI,MAAM,eASJ,UAAU,IAGP;IACC,cAAA;IACA,yBAAA;;EAIF,eA/BR,YAaI,MAAM,eAiBJ,YAAY;EAEV,eAhCR,YAaI,MAAM,eAiBJ,YAAY,IAET;EACD,eAjCR,YAaI,MAAM,eAiBJ,YAAY,IAGT;IACC,cAAA;IACA,6BAAA;;;AAjGZ,eA6GE;EACE,cAAA;;AACA,eAFF,aAEG;EACC,cAAA;;AAQN;EACE,yBAAA;EACA,qBAAA;;AAFF,eAIE;EACE,cAAA;;AACA,eAFF,cAEG;AACD,eAHF,cAGG;EACC,cAAA;EACA,6BAAA;;AATN,eAaE;EACE,cAAA;;AAdJ,eAiBE,YACE,KAAK;EACH,cAAA;;AAEA,eAJJ,YACE,KAAK,IAGF;AACD,eALJ,YACE,KAAK,IAIF;EACC,cAAA;EACA,6BAAA;;AAIF,eAXJ,YAUE,UAAU;AAER,eAZJ,YAUE,UAAU,IAEP;AACD,eAbJ,YAUE,UAAU,IAGP;EACC,cAAA;EACA,yBAAA;;AAIF,eAnBJ,YAkBE,YAAY;AAEV,eApBJ,YAkBE,YAAY,IAET;AACD,eArBJ,YAkBE,YAAY,IAGT;EACC,cAAA;EACA,6BAAA;;AAxCR,eA8CE;EACE,qBAAA;;AACA,eAFF,eAEG;AACD,eAHF,eAGG;EACC,yBAAA;;AAlDN,eA8CE,eAME;EACE,yBAAA;;AArDN,eAyDE;AAzDF,eA0DE;EACE,qBAAA;;AAME,eAFJ,YACE,QAAQ;AAEN,eAHJ,YACE,QAAQ,IAEL;AACD,eAJJ,YACE,QAAQ,IAGL;EACC,yBAAA;EACA,cAAA;;AAuCN,QAnCiD;EAmCjD,eA7CA,YAYI,MAAM,eACJ;IACE,qBAAA;;EA+BR,eA7CA,YAYI,MAAM,eAIJ;IACE,yBAAA;;EA4BR,eA7CA,YAYI,MAAM,eAOJ,KAAK;IACH,cAAA;;EACA,eArBR,YAYI,MAAM,eAOJ,KAAK,IAEF;EACD,eAtBR,YAYI,MAAM,eAOJ,KAAK,IAGF;IACC,cAAA;IACA,6BAAA;;EAIF,eA5BR,YAYI,MAAM,eAeJ,UAAU;EAER,eA7BR,YAYI,MAAM,eAeJ,UAAU,IAEP;EACD,eA9BR,YAYI,MAAM,eAeJ,UAAU,IAGP;IACC,cAAA;IACA,yBAAA;;EAIF,eApCR,YAYI,MAAM,eAuBJ,YAAY;EAEV,eArCR,YAYI,MAAM,eAuBJ,YAAY,IAET;EACD,eAtCR,YAYI,MAAM,eAuBJ,YAAY,IAGT;IACC,cAAA;IACA,6BAAA;;;AAvGZ,eA8GE;EACE,cAAA;;AACA,eAFF,aAEG;EACC,cAAA;;AE9lBN;EACE,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,yBAAA;EACA,kBAAA;;AALF,WAOE;EACE,qBAAA;;AARJ,WAOE,KAGE,KAAI;EACF,SAAS,QAAT;EACA,cAAA;EACA,cAAA;;AAbN,WAiBE;EACE,cAAA;;ACpBJ;EACE,qBAAA;EACA,eAAA;EACA,cAAA;EACA,kBAAA;;AAJF,WAME;EACE,eAAA;;AAPJ,WAME,KAEE;AARJ,WAME,KAGE;EACE,kBAAA;EACA,WAAA;EACA,iBAAA;EACA,uBAAA;EACA,qBAAA;EACA,cAAA;EACA,yBAAA;EACA,yBAAA;EACA,iBAAA;;AAEF,WAdF,KAcG,YACC;AADF,WAdF,KAcG,YAEC;EACE,cAAA;EnBqFN,8BAAA;EACG,2BAAA;;AmBlFD,WArBF,KAqBG,WACC;AADF,WArBF,KAqBG,WAEC;EnBuEJ,+BAAA;EACG,4BAAA;;AmBhED,WAFF,KAAK,IAEF;AAAD,WADF,KAAK,OACF;AACD,WAHF,KAAK,IAGF;AAAD,WAFF,KAAK,OAEF;EACC,cAAA;EACA,yBAAA;EACA,qBAAA;;AAMF,WAFF,UAAU;AAER,WADF,UAAU;AAER,WAHF,UAAU,IAGP;AAAD,WAFF,UAAU,OAEP;AACD,WAJF,UAAU,IAIP;AAAD,WAHF,UAAU,OAGP;EACC,UAAA;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;EACA,eAAA;;AAtDN,WA0DE,YACE;AA3DJ,WA0DE,YAEE,OAAM;AA5DV,WA0DE,YAGE,OAAM;AA7DV,WA0DE,YAIE;AA9DJ,WA0DE,YAKE,IAAG;AA/DP,WA0DE,YAME,IAAG;EACD,cAAA;EACA,yBAAA;EACA,qBAAA;EACA,mBAAA;;AASN,cnBodE,KACE;AmBrdJ,cnBodE,KAEE;EACE,kBAAA;EACA,eAAA;;AAEF,cANF,KAMG,YACC;AADF,cANF,KAMG,YAEC;EA7bJ,8BAAA;EACG,2BAAA;;AAgcD,cAZF,KAYG,WACC;AADF,cAZF,KAYG,WAEC;EA3cJ,+BAAA;EACG,4BAAA;;AmBnBL,cnB+cE,KACE;AmBhdJ,cnB+cE,KAEE;EACE,iBAAA;EACA,eAAA;;AAEF,cANF,KAMG,YACC;AADF,cANF,KAMG,YAEC;EA7bJ,8BAAA;EACG,2BAAA;;AAgcD,cAZF,KAYG,WACC;AADF,cAZF,KAYG,WAEC;EA3cJ,+BAAA;EACG,4BAAA;;AoBnGL;EACE,eAAA;EACA,cAAA;EACA,gBAAA;EACA,kBAAA;;AAJF,MAME;EACE,eAAA;;AAPJ,MAME,GAEE;AARJ,MAME,GAGE;EACE,qBAAA;EACA,iBAAA;EACA,yBAAA;EACA,yBAAA;EACA,mBAAA;;AAdN,MAME,GAWE,IAAG;AAjBP,MAME,GAYE,IAAG;EACD,qBAAA;EACA,yBAAA;;AApBN,MAwBE,MACE;AAzBJ,MAwBE,MAEE;EACE,YAAA;;AA3BN,MA+BE,UACE;AAhCJ,MA+BE,UAEE;EACE,WAAA;;AAlCN,MAsCE,UACE;AAvCJ,MAsCE,UAEE,IAAG;AAxCP,MAsCE,UAGE,IAAG;AAzCP,MAsCE,UAIE;EACE,cAAA;EACA,yBAAA;EACA,mBAAA;;AC9CN;EACE,eAAA;EACA,uBAAA;EACA,cAAA;EACA,iBAAA;EACA,cAAA;EACA,cAAA;EACA,kBAAA;EACA,mBAAA;EACA,wBAAA;EACA,oBAAA;;AAIE,MADD,MACE;AACD,MAFD,MAEE;EACC,cAAA;EACA,qBAAA;EACA,eAAA;;AAKJ,MAAC;EACC,aAAA;;AAIF,IAAK;EACH,kBAAA;EACA,SAAA;;AAOJ;ErBmhBE,yBAAA;;AAEE,cADD,MACE;AACD,cAFD,MAEE;EACC,yBAAA;;AqBnhBN;ErB+gBE,yBAAA;;AAEE,cADD,MACE;AACD,cAFD,MAEE;EACC,yBAAA;;AqB/gBN;ErB2gBE,yBAAA;;AAEE,cADD,MACE;AACD,cAFD,MAEE;EACC,yBAAA;;AqB3gBN;ErBugBE,yBAAA;;AAEE,WADD,MACE;AACD,WAFD,MAEE;EACC,yBAAA;;AqBvgBN;ErBmgBE,yBAAA;;AAEE,cADD,MACE;AACD,cAFD,MAEE;EACC,yBAAA;;AqBngBN;ErB+fE,yBAAA;;AAEE,aADD,MACE;AACD,aAFD,MAEE;EACC,yBAAA;;AsB1jBN;EACE,qBAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;EACA,iBAAA;EACA,cAAA;EACA,cAAA;EACA,wBAAA;EACA,mBAAA;EACA,kBAAA;EACA,yBAAA;EACA,mBAAA;;AAGA,MAAC;EACC,aAAA;;AAIF,IAAK;EACH,kBAAA;EACA,SAAA;;AAEF,OAAQ;EACN,MAAA;EACA,gBAAA;;AAMF,CADD,MACE;AACD,CAFD,MAEE;EACC,cAAA;EACA,qBAAA;EACA,eAAA;;AAKJ,CAAC,gBAAgB,OAAQ;AACzB,UAAW,UAAU,IAAI;EACvB,cAAA;EACA,yBAAA;;AAEF,UAAW,KAAK,IAAI;EAClB,gBAAA;;AChDF;EACE,aAAA;EACA,mBAAA;EACA,cAAA;EACA,yBAAA;;AAJF,UAME;AANF,UAOE;EACE,cAAA;;AARJ,UAUE;EACE,mBAAA;EACA,eAAA;EACA,gBAAA;;AAGF,UAAW;EACT,kBAAA;;AAjBJ,UAoBE;EACE,eAAA;;AAiBJ,mBAdgD;EAchD;IAbI,iBAAA;IACA,oBAAA;;EAEA,UAAW;IACT,kBAAA;IACA,mBAAA;;EAQN,UALI;EAKJ,UAJI;IACE,eAAA;;;ArBlCN;EACE,cAAA;EACA,YAAA;EACA,mBAAA;EACA,uBAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;EFkHA,wCAAA;EACQ,gCAAA;;AE1HV,UAUE;AAVF,UAWE,EAAE;EAEA,iBAAA;EACA,kBAAA;;AAIF,CAAC,UAAC;AACF,CAAC,UAAC;AACF,CAAC,UAAC;EACA,qBAAA;;AArBJ,UAyBE;EACE,YAAA;EACA,cAAA;;AsBzBJ;EACE,aAAA;EACA,mBAAA;EACA,6BAAA;EACA,kBAAA;;AAJF,MAOE;EACE,aAAA;EAEA,cAAA;;AAVJ,MAaE;EACE,iBAAA;;AAdJ,MAkBE;AAlBF,MAmBE;EACE,gBAAA;;AApBJ,MAsBE,IAAI;EACF,eAAA;;AAQJ;EACC,mBAAA;;AADD,kBAIE;EACE,kBAAA;EACA,SAAA;EACA,YAAA;EACA,cAAA;;AAQJ;ExBmXE,yBAAA;EACA,qBAAA;EACA,cAAA;;AwBrXF,cxBuXE;EACE,yBAAA;;AwBxXJ,cxB0XE;EACE,cAAA;;AwBxXJ;ExBgXE,yBAAA;EACA,qBAAA;EACA,cAAA;;AwBlXF,WxBoXE;EACE,yBAAA;;AwBrXJ,WxBuXE;EACE,cAAA;;AwBrXJ;ExB6WE,yBAAA;EACA,qBAAA;EACA,cAAA;;AwB/WF,cxBiXE;EACE,yBAAA;;AwBlXJ,cxBoXE;EACE,cAAA;;AwBlXJ;ExB0WE,yBAAA;EACA,qBAAA;EACA,cAAA;;AwB5WF,axB8WE;EACE,yBAAA;;AwB/WJ,axBiXE;EACE,cAAA;;AyBzaJ;EACE;IAAQ,2BAAA;;EACR;IAAQ,wBAAA;;;AAIV;EACE;IAAQ,2BAAA;;EACR;IAAQ,wBAAA;;;AASV;EACE,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,yBAAA;EACA,kBAAA;EzB0FA,sDAAA;EACQ,8CAAA;;AyBtFV;EACE,WAAA;EACA,SAAA;EACA,YAAA;EACA,eAAA;EACA,iBAAA;EACA,cAAA;EACA,kBAAA;EACA,yBAAA;EzB6EA,sDAAA;EACQ,8CAAA;EAKR,mCAAA;EACQ,2BAAA;;AyB9EV,iBAAkB;EzBqSd,kBAAkB,2LAAlB;EACA,kBAAkB,mLAAlB;EyBpSF,0BAAA;;AAIF,SAAS,OAAQ;EzBoJf,0DAAA;EACQ,kDAAA;;AyB5IV;EzBkiBE,yBAAA;;AACA,iBAAkB;EA7QhB,kBAAkB,2LAAlB;EACA,kBAAkB,mLAAlB;;AyBnRJ;EzB8hBE,yBAAA;;AACA,iBAAkB;EA7QhB,kBAAkB,2LAAlB;EACA,kBAAkB,mLAAlB;;AyB/QJ;EzB0hBE,yBAAA;;AACA,iBAAkB;EA7QhB,kBAAkB,2LAAlB;EACA,kBAAkB,mLAAlB;;AyB3QJ;EzBshBE,yBAAA;;AACA,iBAAkB;EA7QhB,kBAAkB,2LAAlB;EACA,kBAAkB,mLAAlB;;A0B/UJ;AACA;EACE,gBAAA;EACA,OAAA;;AAIF;AACA,MAAO;EACL,gBAAA;;AAEF,MAAM;EACJ,aAAA;;AAIF;EACE,cAAA;;AAIF;EACE,eAAA;;AAOF,MACE;EACE,kBAAA;;AAFJ,MAIE;EACE,iBAAA;;AASJ;EACE,eAAA;EACA,gBAAA;;AC7CF;EAEE,mBAAA;EACA,eAAA;;AAQF;EACE,kBAAA;EACA,cAAA;EACA,kBAAA;EAEA,mBAAA;EACA,yBAAA;EACA,yBAAA;;AAGA,gBAAC;E3BqED,4BAAA;EACC,2BAAA;;A2BnED,gBAAC;EACC,gBAAA;E3ByEF,+BAAA;EACC,8BAAA;;A2BxFH,gBAmBE;EACE,YAAA;;AApBJ,gBAsBE,SAAS;EACP,iBAAA;;AAUJ,CAAC;EACC,cAAA;;AADF,CAAC,gBAGC;EACE,cAAA;;AAIF,CARD,gBAQE;AACD,CATD,gBASE;EACC,qBAAA;EACA,yBAAA;;AAIF,CAfD,gBAeE;AACD,CAhBD,gBAgBE,OAAO;AACR,CAjBD,gBAiBE,OAAO;EACN,UAAA;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AANF,CAfD,gBAeE,OASC;AARF,CAhBD,gBAgBE,OAAO,MAQN;AAPF,CAjBD,gBAiBE,OAAO,MAON;EACE,cAAA;;AAVJ,CAfD,gBAeE,OAYC;AAXF,CAhBD,gBAgBE,OAAO,MAWN;AAVF,CAjBD,gBAiBE,OAAO,MAUN;EACE,cAAA;;A3BoYJ,iBAAiB;EACf,cAAA;EACA,yBAAA;;AAEA,CAAC,iBAJc;EAKb,cAAA;;AADF,CAAC,iBAJc,OAOb;EAA2B,cAAA;;AAE3B,CALD,iBAJc,OASZ;AACD,CAND,iBAJc,OAUZ;EACC,cAAA;EACA,yBAAA;;AAEF,CAVD,iBAJc,OAcZ;AACD,CAXD,iBAJc,OAeZ,OAAO;AACR,CAZD,iBAJc,OAgBZ,OAAO;EACN,WAAA;EACA,yBAAA;EACA,qBAAA;;AAnBN,iBAAiB;EACf,cAAA;EACA,yBAAA;;AAEA,CAAC,iBAJc;EAKb,cAAA;;AADF,CAAC,iBAJc,IAOb;EAA2B,cAAA;;AAE3B,CALD,iBAJc,IASZ;AACD,CAND,iBAJc,IAUZ;EACC,cAAA;EACA,yBAAA;;AAEF,CAVD,iBAJc,IAcZ;AACD,CAXD,iBAJc,IAeZ,OAAO;AACR,CAZD,iBAJc,IAgBZ,OAAO;EACN,WAAA;EACA,yBAAA;EACA,qBAAA;;AAnBN,iBAAiB;EACf,cAAA;EACA,yBAAA;;AAEA,CAAC,iBAJc;EAKb,cAAA;;AADF,CAAC,iBAJc,OAOb;EAA2B,cAAA;;AAE3B,CALD,iBAJc,OASZ;AACD,CAND,iBAJc,OAUZ;EACC,cAAA;EACA,yBAAA;;AAEF,CAVD,iBAJc,OAcZ;AACD,CAXD,iBAJc,OAeZ,OAAO;AACR,CAZD,iBAJc,OAgBZ,OAAO;EACN,WAAA;EACA,yBAAA;EACA,qBAAA;;AAnBN,iBAAiB;EACf,cAAA;EACA,yBAAA;;AAEA,CAAC,iBAJc;EAKb,cAAA;;AADF,CAAC,iBAJc,MAOb;EAA2B,cAAA;;AAE3B,CALD,iBAJc,MASZ;AACD,CAND,iBAJc,MAUZ;EACC,cAAA;EACA,yBAAA;;AAEF,CAVD,iBAJc,MAcZ;AACD,CAXD,iBAJc,MAeZ,OAAO;AACR,CAZD,iBAJc,MAgBZ,OAAO;EACN,WAAA;EACA,yBAAA;EACA,qBAAA;;A2BlYR;EACE,aAAA;EACA,kBAAA;;AAEF;EACE,gBAAA;EACA,gBAAA;;ACtGF;EACE,mBAAA;EACA,yBAAA;EACA,6BAAA;EACA,kBAAA;E5B+GA,iDAAA;EACQ,yCAAA;;A4B3GV;EACE,aAAA;;AAKF;EACE,kBAAA;EACA,oCAAA;E5B4EA,4BAAA;EACC,2BAAA;;A4B/EH,cAKE,YAAY;EACV,cAAA;;AAKJ;EACE,aAAA;EACA,gBAAA;EACA,eAAA;EACA,cAAA;;AAJF,YAME;EACE,cAAA;;AAKJ;EACE,kBAAA;EACA,yBAAA;EACA,6BAAA;E5B4DA,+BAAA;EACC,8BAAA;;A4BnDH,MACE;EACE,gBAAA;;AAFJ,MACE,cAGE;EACE,mBAAA;EACA,gBAAA;;AAIF,MATF,cASG,YACC,iBAAgB;EACd,aAAA;E5B8BN,4BAAA;EACC,2BAAA;;A4B1BC,MAhBF,cAgBG,WACC,iBAAgB;EACd,gBAAA;E5B+BN,+BAAA;EACC,8BAAA;;A4BzBH,cAAe,cACb,iBAAgB;EACd,mBAAA;;AAUJ,MACE;AADF,MAEE,oBAAoB;EAClB,gBAAA;;AAHJ,MAME,SAAQ;AANV,MAOE,oBAAmB,YAAa,SAAQ;E5BHxC,4BAAA;EACC,2BAAA;;A4BLH,MAME,SAAQ,YAIN,QAAO,YAEL,KAAI,YACF,GAAE;AAbV,MAOE,oBAAmB,YAAa,SAAQ,YAGtC,QAAO,YAEL,KAAI,YACF,GAAE;AAbV,MAME,SAAQ,YAKN,QAAO,YACL,KAAI,YACF,GAAE;AAbV,MAOE,oBAAmB,YAAa,SAAQ,YAItC,QAAO,YACL,KAAI,YACF,GAAE;AAbV,MAME,SAAQ,YAIN,QAAO,YAEL,KAAI,YAEF,GAAE;AAdV,MAOE,oBAAmB,YAAa,SAAQ,YAGtC,QAAO,YAEL,KAAI,YAEF,GAAE;AAdV,MAME,SAAQ,YAKN,QAAO,YACL,KAAI,YAEF,GAAE;AAdV,MAOE,oBAAmB,YAAa,SAAQ,YAItC,QAAO,YACL,KAAI,YAEF,GAAE;EACA,2BAAA;;AAfV,MAME,SAAQ,YAIN,QAAO,YAEL,KAAI,YAKF,GAAE;AAjBV,MAOE,oBAAmB,YAAa,SAAQ,YAGtC,QAAO,YAEL,KAAI,YAKF,GAAE;AAjBV,MAME,SAAQ,YAKN,QAAO,YACL,KAAI,YAKF,GAAE;AAjBV,MAOE,oBAAmB,YAAa,SAAQ,YAItC,QAAO,YACL,KAAI,YAKF,GAAE;AAjBV,MAME,SAAQ,YAIN,QAAO,YAEL,KAAI,YAMF,GAAE;AAlBV,MAOE,oBAAmB,YAAa,SAAQ,YAGtC,QAAO,YAEL,KAAI,YAMF,GAAE;AAlBV,MAME,SAAQ,YAKN,QAAO,YACL,KAAI,YAMF,GAAE;AAlBV,MAOE,oBAAmB,YAAa,SAAQ,YAItC,QAAO,YACL,KAAI,YAMF,GAAE;EACA,4BAAA;;AAnBV,MAyBE,SAAQ;AAzBV,MA0BE,oBAAmB,WAAY,SAAQ;E5BdvC,+BAAA;EACC,8BAAA;;A4BbH,MAyBE,SAAQ,WAIN,QAAO,WAEL,KAAI,WACF,GAAE;AAhCV,MA0BE,oBAAmB,WAAY,SAAQ,WAGrC,QAAO,WAEL,KAAI,WACF,GAAE;AAhCV,MAyBE,SAAQ,WAKN,QAAO,WACL,KAAI,WACF,GAAE;AAhCV,MA0BE,oBAAmB,WAAY,SAAQ,WAIrC,QAAO,WACL,KAAI,WACF,GAAE;AAhCV,MAyBE,SAAQ,WAIN,QAAO,WAEL,KAAI,WAEF,GAAE;AAjCV,MA0BE,oBAAmB,WAAY,SAAQ,WAGrC,QAAO,WAEL,KAAI,WAEF,GAAE;AAjCV,MAyBE,SAAQ,WAKN,QAAO,WACL,KAAI,WAEF,GAAE;AAjCV,MA0BE,oBAAmB,WAAY,SAAQ,WAIrC,QAAO,WACL,KAAI,WAEF,GAAE;EACA,8BAAA;;AAlCV,MAyBE,SAAQ,WAIN,QAAO,WAEL,KAAI,WAKF,GAAE;AApCV,MA0BE,oBAAmB,WAAY,SAAQ,WAGrC,QAAO,WAEL,KAAI,WAKF,GAAE;AApCV,MAyBE,SAAQ,WAKN,QAAO,WACL,KAAI,WAKF,GAAE;AApCV,MA0BE,oBAAmB,WAAY,SAAQ,WAIrC,QAAO,WACL,KAAI,WAKF,GAAE;AApCV,MAyBE,SAAQ,WAIN,QAAO,WAEL,KAAI,WAMF,GAAE;AArCV,MA0BE,oBAAmB,WAAY,SAAQ,WAGrC,QAAO,WAEL,KAAI,WAMF,GAAE;AArCV,MAyBE,SAAQ,WAKN,QAAO,WACL,KAAI,WAMF,GAAE;AArCV,MA0BE,oBAAmB,WAAY,SAAQ,WAIrC,QAAO,WACL,KAAI,WAMF,GAAE;EACA,+BAAA;;AAtCV,MA2CE,cAAc;AA3ChB,MA4CE,cAAc;EACZ,6BAAA;;AA7CJ,MA+CE,SAAS,QAAO,YAAa,KAAI,YAAa;AA/ChD,MAgDE,SAAS,QAAO,YAAa,KAAI,YAAa;EAC5C,aAAA;;AAjDJ,MAmDE;AAnDF,MAoDE,oBAAoB;EAClB,SAAA;;AArDJ,MAmDE,kBAGE,QAGE,KACE,KAAI;AA1DZ,MAoDE,oBAAoB,kBAElB,QAGE,KACE,KAAI;AA1DZ,MAmDE,kBAIE,QAEE,KACE,KAAI;AA1DZ,MAoDE,oBAAoB,kBAGlB,QAEE,KACE,KAAI;AA1DZ,MAmDE,kBAKE,QACE,KACE,KAAI;AA1DZ,MAoDE,oBAAoB,kBAIlB,QACE,KACE,KAAI;AA1DZ,MAmDE,kBAGE,QAGE,KAEE,KAAI;AA3DZ,MAoDE,oBAAoB,kBAElB,QAGE,KAEE,KAAI;AA3DZ,MAmDE,kBAIE,QAEE,KAEE,KAAI;AA3DZ,MAoDE,oBAAoB,kBAGlB,QAEE,KAEE,KAAI;AA3DZ,MAmDE,kBAKE,QACE,KAEE,KAAI;AA3DZ,MAoDE,oBAAoB,kBAIlB,QACE,KAEE,KAAI;EACF,cAAA;;AA5DV,MAmDE,kBAGE,QAGE,KAKE,KAAI;AA9DZ,MAoDE,oBAAoB,kBAElB,QAGE,KAKE,KAAI;AA9DZ,MAmDE,kBAIE,QAEE,KAKE,KAAI;AA9DZ,MAoDE,oBAAoB,kBAGlB,QAEE,KAKE,KAAI;AA9DZ,MAmDE,kBAKE,QACE,KAKE,KAAI;AA9DZ,MAoDE,oBAAoB,kBAIlB,QACE,KAKE,KAAI;AA9DZ,MAmDE,kBAGE,QAGE,KAME,KAAI;AA/DZ,MAoDE,oBAAoB,kBAElB,QAGE,KAME,KAAI;AA/DZ,MAmDE,kBAIE,QAEE,KAME,KAAI;AA/DZ,MAoDE,oBAAoB,kBAGlB,QAEE,KAME,KAAI;AA/DZ,MAmDE,kBAKE,QACE,KAME,KAAI;AA/DZ,MAoDE,oBAAoB,kBAIlB,QACE,KAME,KAAI;EACF,eAAA;;AAhEV,MAmDE,kBAiBE,QAEE,KAAI,YACF;AAvER,MAoDE,oBAAoB,kBAgBlB,QAEE,KAAI,YACF;AAvER,MAmDE,kBAkBE,QACE,KAAI,YACF;AAvER,MAoDE,oBAAoB,kBAiBlB,QACE,KAAI,YACF;AAvER,MAmDE,kBAiBE,QAEE,KAAI,YAEF;AAxER,MAoDE,oBAAoB,kBAgBlB,QAEE,KAAI,YAEF;AAxER,MAmDE,kBAkBE,QACE,KAAI,YAEF;AAxER,MAoDE,oBAAoB,kBAiBlB,QACE,KAAI,YAEF;EACE,gBAAA;;AAzEV,MAmDE,kBA0BE,QAEE,KAAI,WACF;AAhFR,MAoDE,oBAAoB,kBAyBlB,QAEE,KAAI,WACF;AAhFR,MAmDE,kBA2BE,QACE,KAAI,WACF;AAhFR,MAoDE,oBAAoB,kBA0BlB,QACE,KAAI,WACF;AAhFR,MAmDE,kBA0BE,QAEE,KAAI,WAEF;AAjFR,MAoDE,oBAAoB,kBAyBlB,QAEE,KAAI,WAEF;AAjFR,MAmDE,kBA2BE,QACE,KAAI,WAEF;AAjFR,MAoDE,oBAAoB,kBA0BlB,QACE,KAAI,WAEF;EACE,gBAAA;;AAlFV,MAuFE;EACE,SAAA;EACA,gBAAA;;AAUJ;EACE,mBAAA;;AADF,YAIE;EACE,gBAAA;EACA,kBAAA;EACA,gBAAA;;AAPJ,YAIE,OAIE;EACE,eAAA;;AATN,YAaE;EACE,gBAAA;;AAdJ,YAaE,eAEE,kBAAkB;EAChB,6BAAA;;AAhBN,YAmBE;EACE,aAAA;;AApBJ,YAmBE,cAEE,kBAAkB;EAChB,gCAAA;;AAON;E5BsLE,qBAAA;;AAEA,cAAE;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AAHF,cAAE,iBAKA,kBAAkB;EAChB,yBAAA;;AAGJ,cAAE,gBACA,kBAAkB;EAChB,4BAAA;;A4BhMN;E5BmLE,qBAAA;;AAEA,cAAE;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AAHF,cAAE,iBAKA,kBAAkB;EAChB,yBAAA;;AAGJ,cAAE,gBACA,kBAAkB;EAChB,4BAAA;;A4B7LN;E5BgLE,qBAAA;;AAEA,cAAE;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AAHF,cAAE,iBAKA,kBAAkB;EAChB,yBAAA;;AAGJ,cAAE,gBACA,kBAAkB;EAChB,4BAAA;;A4B1LN;E5B6KE,qBAAA;;AAEA,WAAE;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AAHF,WAAE,iBAKA,kBAAkB;EAChB,yBAAA;;AAGJ,WAAE,gBACA,kBAAkB;EAChB,4BAAA;;A4BvLN;E5B0KE,qBAAA;;AAEA,cAAE;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AAHF,cAAE,iBAKA,kBAAkB;EAChB,yBAAA;;AAGJ,cAAE,gBACA,kBAAkB;EAChB,4BAAA;;A4BpLN;E5BuKE,qBAAA;;AAEA,aAAE;EACA,cAAA;EACA,yBAAA;EACA,qBAAA;;AAHF,aAAE,iBAKA,kBAAkB;EAChB,yBAAA;;AAGJ,aAAE,gBACA,kBAAkB;EAChB,4BAAA;;A6B5ZN;EACE,gBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;EACA,yBAAA;EACA,kBAAA;E7B6GA,uDAAA;EACQ,+CAAA;;A6BpHV,KAQE;EACE,kBAAA;EACA,iCAAA;;AAKJ;EACE,aAAA;EACA,kBAAA;;AAEF;EACE,YAAA;EACA,kBAAA;;ACtBF;EACE,YAAA;EACA,eAAA;EACA,iBAAA;EACA,cAAA;EACA,cAAA;EACA,4BAAA;E9BkRA,YAAA;EAGA,yBAAA;;A8BlRA,MAAC;AACD,MAAC;EACC,cAAA;EACA,qBAAA;EACA,eAAA;E9B2QF,YAAA;EAGA,yBAAA;;A8BvQA,MAAM;EACJ,UAAA;EACA,eAAA;EACA,uBAAA;EACA,SAAA;EACA,wBAAA;;ACpBJ;EACE,gBAAA;;AAIF;EACE,aAAA;EACA,cAAA;EACA,kBAAA;EACA,eAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;EACA,OAAA;EACA,aAAA;EACA,iCAAA;EAIA,UAAA;;AAGA,MAAC,KAAM;E/BiIP,mBAAmB,kBAAnB;EACI,eAAe,kBAAf;EACI,WAAW,kBAAX;EApBR,mDAAA;EACG,6CAAA;EACE,yCAAA;EACG,mCAAA;;A+B9GR,MAAC,GAAI;E/B6HL,mBAAmB,eAAnB;EACI,eAAe,eAAf;EACI,WAAW,eAAX;;A+B3HV;EACE,kBAAA;EACA,WAAA;EACA,YAAA;;AAIF;EACE,kBAAA;EACA,yBAAA;EACA,yBAAA;EACA,oCAAA;EACA,kBAAA;E/BqEA,gDAAA;EACQ,wCAAA;E+BpER,4BAAA;EAEA,aAAA;;AAIF;EACE,eAAA;EACA,MAAA;EACA,QAAA;EACA,SAAA;EACA,OAAA;EACA,aAAA;EACA,yBAAA;;AAEA,eAAC;E/BwND,UAAA;EAGA,wBAAA;;A+B1NA,eAAC;E/BuND,YAAA;EAGA,yBAAA;;A+BrNF;EACE,aAAA;EACA,gCAAA;EACA,yBAAA;;AAGF,aAAc;EACZ,gBAAA;;AAIF;EACE,SAAA;EACA,uBAAA;;AAKF;EACE,kBAAA;EACA,aAAA;;AAIF;EACE,gBAAA;EACA,uBAAA;EACA,iBAAA;EACA,6BAAA;;AAJF,aAQE,KAAK;EACH,gBAAA;EACA,gBAAA;;AAVJ,aAaE,WAAW,KAAK;EACd,iBAAA;;AAdJ,aAiBE,WAAW;EACT,cAAA;;AAmBJ,QAdmC;EAEjC;IACE,YAAA;IACA,iBAAA;;EAEF;I/BPA,iDAAA;IACQ,yCAAA;;E+BWR;IAAY,YAAA;;;AAMd,QAHmC;EACjC;IAAY,YAAA;;;ACnId;EACE,kBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,eAAA;EACA,gBAAA;EhCiRA,UAAA;EAGA,wBAAA;;AgCjRA,QAAC;EhC8QD,YAAA;EAGA,yBAAA;;AgChRA,QAAC;EAAU,gBAAA;EAAmB,cAAA;;AAC9B,QAAC;EAAU,gBAAA;EAAmB,cAAA;;AAC9B,QAAC;EAAU,eAAA;EAAmB,cAAA;;AAC9B,QAAC;EAAU,iBAAA;EAAmB,cAAA;;AAIhC;EACE,gBAAA;EACA,gBAAA;EACA,cAAA;EACA,kBAAA;EACA,qBAAA;EACA,yBAAA;EACA,kBAAA;;AAIF;EACE,kBAAA;EACA,QAAA;EACA,SAAA;EACA,yBAAA;EACA,mBAAA;;AAGA,QAAC,IAAK;EACJ,SAAA;EACA,SAAA;EACA,iBAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,QAAC,SAAU;EACT,SAAA;EACA,SAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,QAAC,UAAW;EACV,SAAA;EACA,UAAA;EACA,uBAAA;EACA,yBAAA;;AAEF,QAAC,MAAO;EACN,QAAA;EACA,OAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;;AAEF,QAAC,KAAM;EACL,QAAA;EACA,QAAA;EACA,gBAAA;EACA,2BAAA;EACA,0BAAA;;AAEF,QAAC,OAAQ;EACP,MAAA;EACA,SAAA;EACA,iBAAA;EACA,uBAAA;EACA,4BAAA;;AAEF,QAAC,YAAa;EACZ,MAAA;EACA,SAAA;EACA,uBAAA;EACA,4BAAA;;AAEF,QAAC,aAAc;EACb,MAAA;EACA,UAAA;EACA,uBAAA;EACA,4BAAA;;ACvFJ;EACE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,aAAA;EACA,aAAA;EACA,gBAAA;EACA,YAAA;EACA,gBAAA;EACA,yBAAA;EACA,4BAAA;EACA,yBAAA;EACA,oCAAA;EACA,kBAAA;EjCuGA,iDAAA;EACQ,yCAAA;EiCpGR,mBAAA;;AAGA,QAAC;EAAW,iBAAA;;AACZ,QAAC;EAAW,iBAAA;;AACZ,QAAC;EAAW,gBAAA;;AACZ,QAAC;EAAW,kBAAA;;AAGd;EACE,SAAA;EACA,iBAAA;EACA,eAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gCAAA;EACA,0BAAA;;AAGF;EACE,iBAAA;;AAQA,QADO;AAEP,QAFO,SAEN;EACC,kBAAA;EACA,cAAA;EACA,QAAA;EACA,SAAA;EACA,yBAAA;EACA,mBAAA;;AAGJ,QAAS;EACP,kBAAA;;AAEF,QAAS,SAAQ;EACf,kBAAA;EACA,SAAS,EAAT;;AAIA,QAAC,IAAK;EACJ,SAAA;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;EACA,qCAAA;EACA,aAAA;;AACA,QAPD,IAAK,SAOH;EACC,SAAS,GAAT;EACA,WAAA;EACA,kBAAA;EACA,sBAAA;EACA,yBAAA;;AAGJ,QAAC,MAAO;EACN,QAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA;EACA,2BAAA;EACA,uCAAA;;AACA,QAPD,MAAO,SAOL;EACC,SAAS,GAAT;EACA,SAAA;EACA,aAAA;EACA,oBAAA;EACA,2BAAA;;AAGJ,QAAC,OAAQ;EACP,SAAA;EACA,kBAAA;EACA,mBAAA;EACA,4BAAA;EACA,wCAAA;EACA,UAAA;;AACA,QAPD,OAAQ,SAON;EACC,SAAS,GAAT;EACA,QAAA;EACA,kBAAA;EACA,mBAAA;EACA,4BAAA;;AAIJ,QAAC,KAAM;EACL,QAAA;EACA,YAAA;EACA,iBAAA;EACA,qBAAA;EACA,0BAAA;EACA,sCAAA;;AACA,QAPD,KAAM,SAOJ;EACC,SAAS,GAAT;EACA,UAAA;EACA,qBAAA;EACA,0BAAA;EACA,aAAA;;A9B1HN;EACE,kBAAA;;AAGF;EACE,kBAAA;EACA,gBAAA;EACA,WAAA;;AAHF,eAKE;EACE,aAAA;EACA,kBAAA;EH8GF,yCAAA;EACQ,iCAAA;;AGtHV,eAKE,QAME;AAXJ,eAKE,QAOE,IAAI;EAEF,cAAA;;AAdN,eAkBE;AAlBF,eAmBE;AAnBF,eAoBE;EAAU,cAAA;;AApBZ,eAsBE;EACE,OAAA;;AAvBJ,eA0BE;AA1BF,eA2BE;EACE,kBAAA;EACA,MAAA;EACA,WAAA;;AA9BJ,eAiCE;EACE,UAAA;;AAlCJ,eAoCE;EACE,WAAA;;AArCJ,eAuCE,QAAO;AAvCT,eAwCE,QAAO;EACL,OAAA;;AAzCJ,eA4CE,UAAS;EACP,WAAA;;AA7CJ,eA+CE,UAAS;EACP,UAAA;;AAQJ;EACE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EHsNA,YAAA;EAGA,yBAAA;EGvNA,eAAA;EACA,cAAA;EACA,kBAAA;EACA,yCAAA;;AAKA,iBAAC;EH8NC,kBAAkB,8BAA8B,mCAAyC,uCAAzF;EACA,kBAAmB,4EAAnB;EACA,2BAAA;EACA,sHAAA;;AG9NF,iBAAC;EACC,UAAA;EACA,QAAA;EHyNA,kBAAkB,8BAA8B,sCAAyC,oCAAzF;EACA,kBAAmB,4EAAnB;EACA,2BAAA;EACA,sHAAA;;AGvNF,iBAAC;AACD,iBAAC;EACC,aAAA;EACA,cAAA;EACA,qBAAA;EH8LF,YAAA;EAGA,yBAAA;;AG9NF,iBAkCE;AAlCF,iBAmCE;AAnCF,iBAoCE;AApCF,iBAqCE;EACE,kBAAA;EACA,QAAA;EACA,UAAA;EACA,qBAAA;;AAzCJ,iBA2CE;AA3CF,iBA4CE;EACE,SAAA;;AA7CJ,iBA+CE;AA/CF,iBAgDE;EACE,UAAA;;AAjDJ,iBAmDE;AAnDF,iBAoDE;EACE,WAAA;EACA,YAAA;EACA,iBAAA;EACA,kBAAA;EACA,kBAAA;;AAIA,iBADF,WACG;EACC,SAAS,OAAT;;AAIF,iBADF,WACG;EACC,SAAS,OAAT;;AAUN;EACE,kBAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,UAAA;EACA,iBAAA;EACA,eAAA;EACA,gBAAA;EACA,kBAAA;;AATF,oBAWE;EACE,qBAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,mBAAA;EACA,eAAA;EAUA,yBAAA;EACA,kCAAA;;AA9BJ,oBAgCE;EACE,SAAA;EACA,WAAA;EACA,YAAA;EACA,yBAAA;;AAOJ;EACE,kBAAA;EACA,SAAA;EACA,UAAA;EACA,YAAA;EACA,WAAA;EACA,iBAAA;EACA,oBAAA;EACA,cAAA;EACA,kBAAA;EACA,yCAAA;;AACA,iBAAE;EACA,iBAAA;;AAkCJ,mBA5B8C;EAG5C,iBACE;EADF,iBAEE;EAFF,iBAGE;EAHF,iBAIE;IACE,WAAA;IACA,YAAA;IACA,iBAAA;IACA,kBAAA;IACA,eAAA;;EAKJ;IACE,SAAA;IACA,UAAA;IACA,oBAAA;;EAIF;IACE,YAAA;;;AHlNF,SAAC;AACD,SAAC;AMXH,UNUG;AMVH,UNWG;AMSH,gBNVG;AMUH,gBNTG;AMkBH,INnBG;AMmBH,INlBG;AQsXH,gBAoBE,YR3YC;AQuXH,gBAoBE,YR1YC;AUkBH,YVnBG;AUmBH,YVlBG;AU8HH,mBAWE,aV1IC;AU+HH,mBAWE,aVzIC;AeZH,IfWG;AeXH,IfYG;AgBVH,OhBSG;AgBTH,OhBUG;AgBUH,chBXG;AgBWH,chBVG;AgB6BH,gBhB9BG;AgB8BH,gBhB7BG;AoBfH,MpBcG;AoBdH,MpBeG;A4BLH,W5BIG;A4BJH,W5BKG;A+B+EH,a/BhFG;A+BgFH,a/B/EG;EACC,SAAS,GAAT;EACA,cAAA;;AAEF,SAAC;AMfH,UNeG;AMKH,gBNLG;AMcH,INdG;AQkXH,gBAoBE,YRtYC;AUcH,YVdG;AU0HH,mBAWE,aVrIC;AehBH,IfgBG;AgBdH,OhBcG;AgBMH,chBNG;AgByBH,gBhBzBG;AoBnBH,MpBmBG;A4BTH,W5BSG;A+B2EH,a/B3EG;EACC,WAAA;;AiBdJ;EjB6BE,cAAA;EACA,iBAAA;EACA,kBAAA;;AiB5BF;EACE,uBAAA;;AAEF;EACE,sBAAA;;AAQF;EACE,wBAAA;;AAEF;EACE,yBAAA;;AAEF;EACE,kBAAA;;AAEF;EjB8CE,WAAA;EACA,kBAAA;EACA,iBAAA;EACA,6BAAA;EACA,SAAA;;AiBzCF;EACE,wBAAA;EACA,6BAAA;;AAOF;EACE,eAAA;;AiBnCF;EACE,mBAAA;;AAKF;AACA;AACA;AACA;ElCylBE,wBAAA;;AkCjlBF,QAHqC;EAGrC;IlCykBE,yBAAA;;EACA,KAAK;IAAK,cAAA;;EACV,EAAE;IAAQ,kBAAA;;EACV,EAAE;EACF,EAAE;IAAQ,mBAAA;;;AkCxkBZ,QAHqC,uBAAgC;EAGrE;IlCokBE,yBAAA;;EACA,KAAK;IAAK,cAAA;;EACV,EAAE;IAAQ,kBAAA;;EACV,EAAE;EACF,EAAE;IAAQ,mBAAA;;;AkCnkBZ,QAHqC,uBAAgC;EAGrE;IlC+jBE,yBAAA;;EACA,KAAK;IAAK,cAAA;;EACV,EAAE;IAAQ,kBAAA;;EACV,EAAE;EACF,EAAE;IAAQ,mBAAA;;;AkC9jBZ,QAHqC;EAGrC;IlC0jBE,yBAAA;;EACA,KAAK;IAAK,cAAA;;EACV,EAAE;IAAQ,kBAAA;;EACV,EAAE;EACF,EAAE;IAAQ,mBAAA;;;AkCxjBZ,QAHqC;EAGrC;IlC4jBE,wBAAA;;;AkCvjBF,QAHqC,uBAAgC;EAGrE;IlCujBE,wBAAA;;;AkCljBF,QAHqC,uBAAgC;EAGrE;IlCkjBE,wBAAA;;;AkC7iBF,QAHqC;EAGrC;IlC6iBE,wBAAA;;;AkCtiBF;ElCsiBE,wBAAA;;AkChiBF;EAAA;IlCwhBE,yBAAA;;EACA,KAAK;IAAK,cAAA;;EACV,EAAE;IAAQ,kBAAA;;EACV,EAAE;EACF,EAAE;IAAQ,mBAAA;;;AkCthBZ;EAAA;IlC0hBE,wBAAA","sourcesContent":["/*! normalize.css v3.0.0 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined in IE 8/9.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9.\n// Hide the `template` element in IE, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9, Safari 5, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari 5 and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari 5, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow displayed oddly in IE 9.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari 5.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8+, and Opera\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}","//\n// Basic print styles\n// --------------------------------------------------\n// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css\n\n@media print {\n\n * {\n text-shadow: none !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n background: transparent !important;\n box-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links for images, or javascript/internal links\n a[href^=\"javascript:\"]:after,\n a[href^=\"#\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .table {\n td,\n th {\n background-color: #fff !important;\n }\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n}\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 62.5%;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: underline;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n","//\n// Mixins\n// --------------------------------------------------\n\n\n// Utilities\n// -------------------------\n\n// Clearfix\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n\n// WebKit-style focus\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n\n// Center-align a block level element\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n\n// Sizing shortcuts\n.size(@width; @height) {\n width: @width;\n height: @height;\n}\n.square(@size) {\n .size(@size; @size);\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n &::-moz-placeholder { color: @color; // Firefox\n opacity: 1; } // See https://github.com/twbs/bootstrap/pull/11526\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Text overflow\n// Requires inline-block or block for proper styling\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`. Note\n// that we cannot chain the mixins together in Less, so they are repeated.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n\n\n\n// CSS3 PROPERTIES\n// --------------------------------------------------\n\n// Single side border-radius\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support the\n// standard `box-shadow` property.\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Transitions\n.transition(@transition) {\n -webkit-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n// Transformations\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n transform: rotate(@degrees);\n}\n.scale(@ratio; @ratio-y...) {\n -webkit-transform: scale(@ratio, @ratio-y);\n -ms-transform: scale(@ratio, @ratio-y); // IE9 only\n transform: scale(@ratio, @ratio-y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n transform: translate(@x, @y);\n}\n.skew(@x; @y) {\n -webkit-transform: skew(@x, @y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n transform: skew(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// User select\n// For selecting text on the page\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n\n// Resize anything\n.resizable(@direction) {\n resize: @direction; // Options: horizontal, vertical, both\n overflow: auto; // Safari fix\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Opacity\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n\n\n\n// GRADIENTS\n// --------------------------------------------------\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, color-stop(@start-color @start-percent), color-stop(@end-color @end-percent)); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n\n// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n\n\n\n// Retina images\n//\n// Short retina mixin for setting background-image and -size\n\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// COMPONENT MIXINS\n// --------------------------------------------------\n\n// Horizontal dividers\n// -------------------------\n// Dividers (basically an hr) within dropdowns and nav lists\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n\n// Panels\n// -------------------------\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse .panel-body {\n border-top-color: @border;\n }\n }\n & > .panel-footer {\n + .panel-collapse .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n\n// Alerts\n// -------------------------\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n\n// Tables\n// -------------------------\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n\n// List Groups\n// -------------------------\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading { color: inherit; }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n\n// Button variants\n// -------------------------\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &:active,\n &.active,\n .open .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 8%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n// -------------------------\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n\n// Pagination\n// -------------------------\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n\n// Labels\n// -------------------------\n.label-variant(@color) {\n background-color: @color;\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n\n// Contextual backgrounds\n// -------------------------\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n\n// Typography\n// -------------------------\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n\n// Navbar vertical align\n// -------------------------\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n\n// Progress bars\n// -------------------------\n.progress-bar-variant(@color) {\n background-color: @color;\n .progress-striped & {\n #gradient > .striped();\n }\n}\n\n// Responsive utilities\n// -------------------------\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n\n\n// Grid System\n// -----------\n\n// Centered container element\n.container-fixed() {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n @media (min-width: @screen-xs-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-xs-column-push(@columns) {\n @media (min-width: @screen-xs-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-xs-column-pull(@columns) {\n @media (min-width: @screen-xs-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n\n// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) when (@index = 1) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n\n// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-focus-border` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `` background color\n@input-bg: #fff;\n//** `` background color\n@input-bg-disabled: @gray-lighter;\n\n//** Text color for ``s\n@input-color: @gray;\n//** `` border color\n@input-border: #ccc;\n//** `` border radius\n@input-border-radius: @border-radius-base;\n//** Border color for inputs on focus\n@input-border-focus: #66afe9;\n\n//** Placeholder text color\n@input-color-placeholder: @gray-light;\n\n//** Default `.form-control` height\n@input-height-base: (@line-height-computed + (@padding-base-vertical * 2) + 2);\n//** Large `.form-control` height\n@input-height-large: (ceil(@font-size-large * @line-height-large) + (@padding-large-vertical * 2) + 2);\n//** Small `.form-control` height\n@input-height-small: (floor(@font-size-small * @line-height-small) + (@padding-small-vertical * 2) + 2);\n\n@legend-color: @gray-dark;\n@legend-border-color: #e5e5e5;\n\n//** Background color for textual input addons\n@input-group-addon-bg: @gray-lighter;\n//** Border color for textual input addons\n@input-group-addon-border-color: @input-border;\n\n\n//== Dropdowns\n//\n//## Dropdown menu container and contents.\n\n//** Background for the dropdown menu.\n@dropdown-bg: #fff;\n//** Dropdown menu `border-color`.\n@dropdown-border: rgba(0,0,0,.15);\n//** Dropdown menu `border-color` **for IE8**.\n@dropdown-fallback-border: #ccc;\n//** Divider color for between dropdown items.\n@dropdown-divider-bg: #e5e5e5;\n\n//** Dropdown link text color.\n@dropdown-link-color: @gray-dark;\n//** Hover color for dropdown links.\n@dropdown-link-hover-color: darken(@gray-dark, 5%);\n//** Hover background for dropdown links.\n@dropdown-link-hover-bg: #f5f5f5;\n\n//** Active dropdown menu item text color.\n@dropdown-link-active-color: @component-active-color;\n//** Active dropdown menu item background color.\n@dropdown-link-active-bg: @component-active-bg;\n\n//** Disabled dropdown menu item background color.\n@dropdown-link-disabled-color: @gray-light;\n\n//** Text color for headers within dropdown menus.\n@dropdown-header-color: @gray-light;\n\n// Note: Deprecated @dropdown-caret-color as of v3.1.0\n@dropdown-caret-color: #000;\n\n\n//-- Z-index master list\n//\n// Warning: Avoid customizing these values. They're used for a bird's eye view\n// of components dependent on the z-axis and are designed to all work together.\n//\n// Note: These variables are not generated into the Customizer.\n\n@zindex-navbar: 1000;\n@zindex-dropdown: 1000;\n@zindex-popover: 1010;\n@zindex-tooltip: 1030;\n@zindex-navbar-fixed: 1030;\n@zindex-modal-background: 1040;\n@zindex-modal: 1050;\n\n\n//== Media queries breakpoints\n//\n//## Define the breakpoints at which your layout will change, adapting to different screen sizes.\n\n// Extra small screen / phone\n// Note: Deprecated @screen-xs and @screen-phone as of v3.0.1\n@screen-xs: 480px;\n@screen-xs-min: @screen-xs;\n@screen-phone: @screen-xs-min;\n\n// Small screen / tablet\n// Note: Deprecated @screen-sm and @screen-tablet as of v3.0.1\n@screen-sm: 768px;\n@screen-sm-min: @screen-sm;\n@screen-tablet: @screen-sm-min;\n\n// Medium screen / desktop\n// Note: Deprecated @screen-md and @screen-desktop as of v3.0.1\n@screen-md: 992px;\n@screen-md-min: @screen-md;\n@screen-desktop: @screen-md-min;\n\n// Large screen / wide desktop\n// Note: Deprecated @screen-lg and @screen-lg-desktop as of v3.0.1\n@screen-lg: 1200px;\n@screen-lg-min: @screen-lg;\n@screen-lg-desktop: @screen-lg-min;\n\n// So media queries don't overlap when required, provide a maximum\n@screen-xs-max: (@screen-sm-min - 1);\n@screen-sm-max: (@screen-md-min - 1);\n@screen-md-max: (@screen-lg-min - 1);\n\n\n//== Grid system\n//\n//## Define your custom responsive grid.\n\n//** Number of columns in the grid.\n@grid-columns: 12;\n//** Padding between columns. Gets divided in half for the left and right.\n@grid-gutter-width: 30px;\n// Navbar collapse\n//** Point at which the navbar becomes uncollapsed.\n@grid-float-breakpoint: @screen-sm-min;\n//** Point at which the navbar begins collapsing.\n@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);\n\n\n//== Container sizes\n//\n//## Define the maximum width of `.container` for different screen sizes.\n\n// Small screen / tablet\n@container-tablet: ((720px + @grid-gutter-width));\n//** For `@screen-sm-min` and up.\n@container-sm: @container-tablet;\n\n// Medium screen / desktop\n@container-desktop: ((940px + @grid-gutter-width));\n//** For `@screen-md-min` and up.\n@container-md: @container-desktop;\n\n// Large screen / wide desktop\n@container-large-desktop: ((1140px + @grid-gutter-width));\n//** For `@screen-lg-min` and up.\n@container-lg: @container-large-desktop;\n\n\n//== Navbar\n//\n//##\n\n// Basics of a navbar\n@navbar-height: 50px;\n@navbar-margin-bottom: @line-height-computed;\n@navbar-border-radius: @border-radius-base;\n@navbar-padding-horizontal: floor((@grid-gutter-width / 2));\n@navbar-padding-vertical: ((@navbar-height - @line-height-computed) / 2);\n@navbar-collapse-max-height: 340px;\n\n@navbar-default-color: #777;\n@navbar-default-bg: #f8f8f8;\n@navbar-default-border: darken(@navbar-default-bg, 6.5%);\n\n// Navbar links\n@navbar-default-link-color: #777;\n@navbar-default-link-hover-color: #333;\n@navbar-default-link-hover-bg: transparent;\n@navbar-default-link-active-color: #555;\n@navbar-default-link-active-bg: darken(@navbar-default-bg, 6.5%);\n@navbar-default-link-disabled-color: #ccc;\n@navbar-default-link-disabled-bg: transparent;\n\n// Navbar brand label\n@navbar-default-brand-color: @navbar-default-link-color;\n@navbar-default-brand-hover-color: darken(@navbar-default-brand-color, 10%);\n@navbar-default-brand-hover-bg: transparent;\n\n// Navbar toggle\n@navbar-default-toggle-hover-bg: #ddd;\n@navbar-default-toggle-icon-bar-bg: #888;\n@navbar-default-toggle-border-color: #ddd;\n\n\n// Inverted navbar\n// Reset inverted navbar basics\n@navbar-inverse-color: @gray-light;\n@navbar-inverse-bg: #222;\n@navbar-inverse-border: darken(@navbar-inverse-bg, 10%);\n\n// Inverted navbar links\n@navbar-inverse-link-color: @gray-light;\n@navbar-inverse-link-hover-color: #fff;\n@navbar-inverse-link-hover-bg: transparent;\n@navbar-inverse-link-active-color: @navbar-inverse-link-hover-color;\n@navbar-inverse-link-active-bg: darken(@navbar-inverse-bg, 10%);\n@navbar-inverse-link-disabled-color: #444;\n@navbar-inverse-link-disabled-bg: transparent;\n\n// Inverted navbar brand label\n@navbar-inverse-brand-color: @navbar-inverse-link-color;\n@navbar-inverse-brand-hover-color: #fff;\n@navbar-inverse-brand-hover-bg: transparent;\n\n// Inverted navbar toggle\n@navbar-inverse-toggle-hover-bg: #333;\n@navbar-inverse-toggle-icon-bar-bg: #fff;\n@navbar-inverse-toggle-border-color: #333;\n\n\n//== Navs\n//\n//##\n\n//=== Shared nav styles\n@nav-link-padding: 10px 15px;\n@nav-link-hover-bg: @gray-lighter;\n\n@nav-disabled-link-color: @gray-light;\n@nav-disabled-link-hover-color: @gray-light;\n\n@nav-open-link-hover-color: #fff;\n\n//== Tabs\n@nav-tabs-border-color: #ddd;\n\n@nav-tabs-link-hover-border-color: @gray-lighter;\n\n@nav-tabs-active-link-hover-bg: @body-bg;\n@nav-tabs-active-link-hover-color: @gray;\n@nav-tabs-active-link-hover-border-color: #ddd;\n\n@nav-tabs-justified-link-border-color: #ddd;\n@nav-tabs-justified-active-link-border-color: @body-bg;\n\n//== Pills\n@nav-pills-border-radius: @border-radius-base;\n@nav-pills-active-link-hover-bg: @component-active-bg;\n@nav-pills-active-link-hover-color: @component-active-color;\n\n\n//== Pagination\n//\n//##\n\n@pagination-color: @link-color;\n@pagination-bg: #fff;\n@pagination-border: #ddd;\n\n@pagination-hover-color: @link-hover-color;\n@pagination-hover-bg: @gray-lighter;\n@pagination-hover-border: #ddd;\n\n@pagination-active-color: #fff;\n@pagination-active-bg: @brand-primary;\n@pagination-active-border: @brand-primary;\n\n@pagination-disabled-color: @gray-light;\n@pagination-disabled-bg: #fff;\n@pagination-disabled-border: #ddd;\n\n\n//== Pager\n//\n//##\n\n@pager-bg: @pagination-bg;\n@pager-border: @pagination-border;\n@pager-border-radius: 15px;\n\n@pager-hover-bg: @pagination-hover-bg;\n\n@pager-active-bg: @pagination-active-bg;\n@pager-active-color: @pagination-active-color;\n\n@pager-disabled-color: @pagination-disabled-color;\n\n\n//== Jumbotron\n//\n//##\n\n@jumbotron-padding: 30px;\n@jumbotron-color: inherit;\n@jumbotron-bg: @gray-lighter;\n@jumbotron-heading-color: inherit;\n@jumbotron-font-size: ceil((@font-size-base * 1.5));\n\n\n//== Form states and alerts\n//\n//## Define colors for form feedback states and, by default, alerts.\n\n@state-success-text: #3c763d;\n@state-success-bg: #dff0d8;\n@state-success-border: darken(spin(@state-success-bg, -10), 5%);\n\n@state-info-text: #31708f;\n@state-info-bg: #d9edf7;\n@state-info-border: darken(spin(@state-info-bg, -10), 7%);\n\n@state-warning-text: #8a6d3b;\n@state-warning-bg: #fcf8e3;\n@state-warning-border: darken(spin(@state-warning-bg, -10), 5%);\n\n@state-danger-text: #a94442;\n@state-danger-bg: #f2dede;\n@state-danger-border: darken(spin(@state-danger-bg, -10), 5%);\n\n\n//== Tooltips\n//\n//##\n\n//** Tooltip max width\n@tooltip-max-width: 200px;\n//** Tooltip text color\n@tooltip-color: #fff;\n//** Tooltip background color\n@tooltip-bg: #000;\n@tooltip-opacity: .9;\n\n//** Tooltip arrow width\n@tooltip-arrow-width: 5px;\n//** Tooltip arrow color\n@tooltip-arrow-color: @tooltip-bg;\n\n\n//== Popovers\n//\n//##\n\n//** Popover body background color\n@popover-bg: #fff;\n//** Popover maximum width\n@popover-max-width: 276px;\n//** Popover border color\n@popover-border-color: rgba(0,0,0,.2);\n//** Popover fallback border color\n@popover-fallback-border-color: #ccc;\n\n//** Popover title background color\n@popover-title-bg: darken(@popover-bg, 3%);\n\n//** Popover arrow width\n@popover-arrow-width: 10px;\n//** Popover arrow color\n@popover-arrow-color: #fff;\n\n//** Popover outer arrow width\n@popover-arrow-outer-width: (@popover-arrow-width + 1);\n//** Popover outer arrow color\n@popover-arrow-outer-color: fadein(@popover-border-color, 5%);\n//** Popover outer arrow fallback color\n@popover-arrow-outer-fallback-color: darken(@popover-fallback-border-color, 20%);\n\n\n//== Labels\n//\n//##\n\n//** Default label background color\n@label-default-bg: @gray-light;\n//** Primary label background color\n@label-primary-bg: @brand-primary;\n//** Success label background color\n@label-success-bg: @brand-success;\n//** Info label background color\n@label-info-bg: @brand-info;\n//** Warning label background color\n@label-warning-bg: @brand-warning;\n//** Danger label background color\n@label-danger-bg: @brand-danger;\n\n//** Default label text color\n@label-color: #fff;\n//** Default text color of a linked label\n@label-link-hover-color: #fff;\n\n\n//== Modals\n//\n//##\n\n//** Padding applied to the modal body\n@modal-inner-padding: 20px;\n\n//** Padding applied to the modal title\n@modal-title-padding: 15px;\n//** Modal title line-height\n@modal-title-line-height: @line-height-base;\n\n//** Background color of modal content area\n@modal-content-bg: #fff;\n//** Modal content border color\n@modal-content-border-color: rgba(0,0,0,.2);\n//** Modal content border color **for IE8**\n@modal-content-fallback-border-color: #999;\n\n//** Modal backdrop background color\n@modal-backdrop-bg: #000;\n//** Modal backdrop opacity\n@modal-backdrop-opacity: .5;\n//** Modal header border color\n@modal-header-border-color: #e5e5e5;\n//** Modal footer border color\n@modal-footer-border-color: @modal-header-border-color;\n\n@modal-lg: 900px;\n@modal-md: 600px;\n@modal-sm: 300px;\n\n\n//== Alerts\n//\n//## Define alert colors, border radius, and padding.\n\n@alert-padding: 15px;\n@alert-border-radius: @border-radius-base;\n@alert-link-font-weight: bold;\n\n@alert-success-bg: @state-success-bg;\n@alert-success-text: @state-success-text;\n@alert-success-border: @state-success-border;\n\n@alert-info-bg: @state-info-bg;\n@alert-info-text: @state-info-text;\n@alert-info-border: @state-info-border;\n\n@alert-warning-bg: @state-warning-bg;\n@alert-warning-text: @state-warning-text;\n@alert-warning-border: @state-warning-border;\n\n@alert-danger-bg: @state-danger-bg;\n@alert-danger-text: @state-danger-text;\n@alert-danger-border: @state-danger-border;\n\n\n//== Progress bars\n//\n//##\n\n//** Background color of the whole progress component\n@progress-bg: #f5f5f5;\n//** Progress bar text color\n@progress-bar-color: #fff;\n\n//** Default progress bar color\n@progress-bar-bg: @brand-primary;\n//** Success progress bar color\n@progress-bar-success-bg: @brand-success;\n//** Warning progress bar color\n@progress-bar-warning-bg: @brand-warning;\n//** Danger progress bar color\n@progress-bar-danger-bg: @brand-danger;\n//** Info progress bar color\n@progress-bar-info-bg: @brand-info;\n\n\n//== List group\n//\n//##\n\n//** Background color on `.list-group-item`\n@list-group-bg: #fff;\n//** `.list-group-item` border color\n@list-group-border: #ddd;\n//** List group border radius\n@list-group-border-radius: @border-radius-base;\n\n//** Background color of single list elements on hover\n@list-group-hover-bg: #f5f5f5;\n//** Text color of active list elements\n@list-group-active-color: @component-active-color;\n//** Background color of active list elements\n@list-group-active-bg: @component-active-bg;\n//** Border color of active list elements\n@list-group-active-border: @list-group-active-bg;\n@list-group-active-text-color: lighten(@list-group-active-bg, 40%);\n\n@list-group-link-color: #555;\n@list-group-link-heading-color: #333;\n\n\n//== Panels\n//\n//##\n\n@panel-bg: #fff;\n@panel-body-padding: 15px;\n@panel-border-radius: @border-radius-base;\n\n//** Border color for elements within panels\n@panel-inner-border: #ddd;\n@panel-footer-bg: #f5f5f5;\n\n@panel-default-text: @gray-dark;\n@panel-default-border: #ddd;\n@panel-default-heading-bg: #f5f5f5;\n\n@panel-primary-text: #fff;\n@panel-primary-border: @brand-primary;\n@panel-primary-heading-bg: @brand-primary;\n\n@panel-success-text: @state-success-text;\n@panel-success-border: @state-success-border;\n@panel-success-heading-bg: @state-success-bg;\n\n@panel-info-text: @state-info-text;\n@panel-info-border: @state-info-border;\n@panel-info-heading-bg: @state-info-bg;\n\n@panel-warning-text: @state-warning-text;\n@panel-warning-border: @state-warning-border;\n@panel-warning-heading-bg: @state-warning-bg;\n\n@panel-danger-text: @state-danger-text;\n@panel-danger-border: @state-danger-border;\n@panel-danger-heading-bg: @state-danger-bg;\n\n\n//== Thumbnails\n//\n//##\n\n//** Padding around the thumbnail image\n@thumbnail-padding: 4px;\n//** Thumbnail background color\n@thumbnail-bg: @body-bg;\n//** Thumbnail border color\n@thumbnail-border: #ddd;\n//** Thumbnail border radius\n@thumbnail-border-radius: @border-radius-base;\n\n//** Custom text color for thumbnail captions\n@thumbnail-caption-color: @text-color;\n//** Padding around the thumbnail caption\n@thumbnail-caption-padding: 9px;\n\n\n//== Wells\n//\n//##\n\n@well-bg: #f5f5f5;\n@well-border: darken(@well-bg, 7%);\n\n\n//== Badges\n//\n//##\n\n@badge-color: #fff;\n//** Linked badge text color on hover\n@badge-link-hover-color: #fff;\n@badge-bg: @gray-light;\n\n//** Badge text color in active nav link\n@badge-active-color: @link-color;\n//** Badge background color in active nav link\n@badge-active-bg: #fff;\n\n@badge-font-weight: bold;\n@badge-line-height: 1;\n@badge-border-radius: 10px;\n\n\n//== Breadcrumbs\n//\n//##\n\n@breadcrumb-padding-vertical: 8px;\n@breadcrumb-padding-horizontal: 15px;\n//** Breadcrumb background color\n@breadcrumb-bg: #f5f5f5;\n//** Breadcrumb text color\n@breadcrumb-color: #ccc;\n//** Text color of current page in the breadcrumb\n@breadcrumb-active-color: @gray-light;\n//** Textual separator for between breadcrumb elements\n@breadcrumb-separator: \"/\";\n\n\n//== Carousel\n//\n//##\n\n@carousel-text-shadow: 0 1px 2px rgba(0,0,0,.6);\n\n@carousel-control-color: #fff;\n@carousel-control-width: 15%;\n@carousel-control-opacity: .5;\n@carousel-control-font-size: 20px;\n\n@carousel-indicator-active-bg: #fff;\n@carousel-indicator-border-color: #fff;\n\n@carousel-caption-color: #fff;\n\n\n//== Close\n//\n//##\n\n@close-font-weight: bold;\n@close-color: #000;\n@close-text-shadow: 0 1px 0 #fff;\n\n\n//== Code\n//\n//##\n\n@code-color: #c7254e;\n@code-bg: #f9f2f4;\n\n@kbd-color: #fff;\n@kbd-bg: #333;\n\n@pre-bg: #f5f5f5;\n@pre-color: @gray-dark;\n@pre-border-color: #ccc;\n@pre-scrollable-max-height: 340px;\n\n\n//== Type\n//\n//##\n\n//** Text muted color\n@text-muted: @gray-light;\n//** Abbreviations and acronyms border color\n@abbr-border-color: @gray-light;\n//** Headings small color\n@headings-small-color: @gray-light;\n//** Blockquote small color\n@blockquote-small-color: @gray-light;\n//** Blockquote font size\n@blockquote-font-size: (@font-size-base * 1.25);\n//** Blockquote border color\n@blockquote-border-color: @gray-lighter;\n//** Page header border color\n@page-header-border-color: @gray-lighter;\n\n\n//== Miscellaneous\n//\n//##\n\n//** Horizontal line color.\n@hr-border: @gray-lighter;\n\n//** Horizontal offset for forms and lists.\n@component-offset-horizontal: 180px;\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n }\n\n > .active,\n > .next,\n > .prev { display: block; }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: none;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n margin-left: -10px;\n font-family: serif;\n }\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n margin-left: -15px;\n font-size: 30px;\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 200;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: 14px base font * 85% = about 12px\nsmall,\n.small { font-size: 85%; }\n\n// Undo browser default styling\ncite { font-style: normal; }\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// --------------------------------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n@media (min-width: @grid-float-breakpoint) {\n .dl-horizontal {\n dt {\n float: left;\n width: (@component-offset-horizontal - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @component-offset-horizontal;\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n }\n}\n\n// MISC\n// ----\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Quotes\nblockquote:before,\nblockquote:after {\n content: \"\";\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n white-space: nowrap;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n max-width: 100%;\n background-color: @table-bg;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-child(odd) {\n > td,\n > th {\n background-color: @table-bg-accent;\n }\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n > td,\n > th {\n background-color: @table-bg-hover;\n }\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9/10 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n@media (max-width: @screen-xs-max) {\n .table-responsive {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n overflow-x: scroll;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n -webkit-overflow-scrolling: touch;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: -webkit-min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; /* IE8-9 */\n line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n cursor: not-allowed;\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS date input\n//\n// In Mobile Safari, date inputs require a pixel line-height that matches the\n// given height of the input.\n\ninput[type=\"date\"] {\n line-height: @input-height-base;\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n display: block;\n min-height: @line-height-computed; // clear the floating input if there is no label text\n margin-top: 10px;\n margin-bottom: 10px;\n padding-left: 20px;\n label {\n display: inline;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n float: left;\n margin-left: -20px;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"],\n.radio,\n.radio-inline,\n.checkbox,\n.checkbox-inline {\n &[disabled],\n fieldset[disabled] & {\n cursor: not-allowed;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n\n.input-sm {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n.input-lg {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n // Enable absolute positioning\n position: relative;\n\n // Ensure icons don't overlap text\n .form-control {\n padding-right: (@input-height-base * 1.25);\n }\n\n // Feedback icon (requires .glyphicon classes)\n .form-control-feedback {\n position: absolute;\n top: (@line-height-computed + 5); // Height of the `label` and its margin\n right: 0;\n display: block;\n width: @input-height-base;\n height: @input-height-base;\n line-height: @input-height-base;\n text-align: center;\n }\n}\n\n// Feedback states\n.has-success {\n .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n margin-bottom: 0; // Remove default margin from `p`\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n display: block; // account for any element using help-block\n margin-top: 5px;\n margin-bottom: 10px;\n color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n // Kick in the inline\n @media (min-width: @screen-sm-min) {\n // Inline-block all the things for \"inline\"\n .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // In navbar-form, allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n // Input groups need that 100% width though\n .input-group > .form-control {\n width: 100%;\n }\n\n .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match (which also avoids\n // a bug in WebKit: https://github.com/twbs/bootstrap/issues/1969).\n .radio,\n .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n padding-left: 0;\n vertical-align: middle;\n }\n .radio input[type=\"radio\"],\n .checkbox input[type=\"checkbox\"] {\n float: none;\n margin-left: 0;\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n top: 0;\n }\n }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n // Consistent vertical alignment of labels, radios, and checkboxes\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n // Account for padding we're adding to ensure the alignment and of help text\n // and other content below items\n .radio,\n .checkbox {\n min-height: (@line-height-computed + (@padding-base-vertical + 1));\n }\n\n // Make form groups behave like rows\n .form-group {\n .make-row();\n }\n\n .form-control-static {\n padding-top: (@padding-base-vertical + 1);\n }\n\n // Only right align form labels here when the columns stop stacking\n @media (min-width: @screen-sm-min) {\n .control-label {\n text-align: right;\n }\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n top: 0;\n right: (@grid-gutter-width / 2);\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: not-allowed;\n pointer-events: none; // Future-proof disabling of clicks\n .opacity(.65);\n .box-shadow(none);\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n cursor: pointer;\n border-radius: 0;\n\n &,\n &:active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: underline;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n padding-left: 0;\n padding-right: 0;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n &:focus {\n // Remove focus outline when dropdown JS adds it after closing the menu\n outline: none;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n border-top-right-radius: @border-radius-base;\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n border-bottom-left-radius: @border-radius-base;\n .border-top-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n}\n\n\n// Checkbox and radio options\n[data-toggle=\"buttons\"] > .btn > input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn > input[type=\"checkbox\"] {\n display: none;\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n &.in {\n display: block;\n }\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition(height .35s ease);\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// Star\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: ~\"url('@{icon-font-path}@{icon-font-name}.eot')\";\n src: ~\"url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype')\",\n ~\"url('@{icon-font-path}@{icon-font-name}.woff') format('woff')\",\n ~\"url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype')\",\n ~\"url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg')\";\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base solid;\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n}\n// Nuke hover/focus effects\n.dropdown-menu > .disabled > a {\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: not-allowed;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base solid;\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 1px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn { .input-lg(); }\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn { .input-sm(); }\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @border-radius-base;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n max-height: @navbar-collapse-max-height;\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: none;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n\n &.navbar-right:last-child {\n margin-right: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right { .pull-right(); }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n\n // Outdent the form if last child to line up with content down the page\n &.navbar-right:last-child {\n margin-right: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n\n // Outdent the form if last child to line up with content down the page\n &.navbar-right:last-child {\n margin-right: 0;\n }\n }\n}\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: not-allowed;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: not-allowed;\n }\n }\n\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n &[href] {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base classes\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n .btn-xs & {\n top: 0;\n padding: 1px 5px;\n }\n}\n\n// Hover state, but only for links\na.badge {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n}\n\n// Account for counters in navs\na.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding: @jumbotron-padding;\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n .container & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding-top: (@jumbotron-padding * 1.6);\n padding-bottom: (@jumbotron-padding * 1.6);\n\n .container & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: (@font-size-base * 4.5);\n }\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissable alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n.progress-striped .progress-bar {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n.progress.active .progress-bar {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Media objects\n// Source: http://stubbornella.org/content/?p=497\n// --------------------------------------------------\n\n\n// Common styles\n// -------------------------\n\n// Clear the floats\n.media,\n.media-body {\n overflow: hidden;\n zoom: 1;\n}\n\n// Proper spacing between instances of .media\n.media,\n.media .media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n\n// For images and videos, set to block\n.media-object {\n display: block;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin: 0 0 5px;\n}\n\n\n// Media image alignment\n// -------------------------\n\n.media {\n > .pull-left {\n margin-right: 10px;\n }\n > .pull-right {\n margin-left: 10px;\n }\n}\n\n\n// Media list variation\n// -------------------------\n\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on
    ,
      , or
      .\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n\n // Align badges within list items\n > .badge {\n float: right;\n }\n > .badge + .badge {\n margin-right: 5px;\n }\n}\n\n\n// Linked list items\n//\n// Use anchor elements instead of `li`s or `div`s to create linked list items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @list-group-hover-bg;\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: 10px 15px;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table {\n margin-bottom: 0;\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n overflow: hidden; // crop contents when collapsed\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n + .panel-collapse .panel-body {\n border-top: 1px solid @panel-inner-border;\n }\n }\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: auto;\n overflow-y: scroll;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0)}\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: none;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal-background;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n min-height: (@modal-title-padding + @modal-title-line-height);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n margin-top: 15px;\n padding: (@modal-inner-padding - 1) @modal-inner-padding @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n visibility: visible;\n font-size: @font-size-small;\n line-height: 1.4;\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n text-decoration: none;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n text-align: left; // Reset given new insertion method\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Overrides for proper insertion\n white-space: normal;\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 18px;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: 5px 5px 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#browsers\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n"]} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.min.css b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.min.css new file mode 100644 index 0000000..679272d --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:gray}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.eot b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000..4a4ca86 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.eot differ diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.svg b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000..e3e2dc7 --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.ttf b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000..67fa00b Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.ttf differ diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.woff b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000..8c54182 Binary files /dev/null and b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/fonts/glyphicons-halflings-regular.woff differ diff --git a/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/js/bootstrap.js b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/js/bootstrap.js new file mode 100644 index 0000000..8ae571b --- /dev/null +++ b/cas-server-webapp/src/main/webapp/assets/plugins/bootstrap/js/bootstrap.js @@ -0,0 +1,1951 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript requires jQuery') } + +/* ======================================================================== + * Bootstrap: transition.js v3.1.1 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + 'WebkitTransition' : 'webkitTransitionEnd', + 'MozTransition' : 'transitionend', + 'OTransition' : 'oTransitionEnd otransitionend', + 'transition' : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false, $el = this + $(this).one($.support.transition.end, function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.1.1 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.hasClass('alert') ? $this : $this.parent() + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + $parent.trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one($.support.transition.end, removeElement) + .emulateTransitionEnd(150) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + var old = $.fn.alert + + $.fn.alert = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.1.1 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state = state + 'Text' + + if (!data.resetText) $el.data('resetText', $el[val]()) + + $el[val](data[state] || this.options[state]) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked') && this.$element.hasClass('active')) changed = false + else $parent.find('.active').removeClass('active') + } + if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') + } + + if (changed) this.$element.toggleClass('active') + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + var old = $.fn.button + + $.fn.button = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + $btn.button('toggle') + e.preventDefault() + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.1.1 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = + this.sliding = + this.interval = + this.$active = + this.$items = null + + this.options.pause == 'hover' && this.$element + .on('mouseenter', $.proxy(this.pause, this)) + .on('mouseleave', $.proxy(this.cycle, this)) + } + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getActiveIndex = function () { + this.$active = this.$element.find('.item.active') + this.$items = this.$active.parent().children() + + return this.$items.index(this.$active) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getActiveIndex() + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || $active[type]() + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var fallback = type == 'next' ? 'first' : 'last' + var that = this + + if (!$next.length) { + if (!this.options.wrap) return + $next = this.$element.find('.item')[fallback]() + } + + if ($next.hasClass('active')) return this.sliding = false + + var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction }) + this.$element.trigger(e) + if (e.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid.bs.carousel', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one($.support.transition.end, function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { that.$element.trigger('slid.bs.carousel') }, 0) + }) + .emulateTransitionEnd($active.css('transition-duration').slice(0, -1) * 1000) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger('slid.bs.carousel') + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + var old = $.fn.carousel + + $.fn.carousel = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { + var $this = $(this), href + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + }) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + $carousel.carousel($carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.1.1 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.transitioning = null + + if (this.options.parent) this.$parent = $(this.options.parent) + if (this.options.toggle) this.toggle() + } + + Collapse.DEFAULTS = { + toggle: true + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var actives = this.$parent && this.$parent.find('> .panel > .in') + + if (actives && actives.length) { + var hasData = actives.data('bs.collapse') + if (hasData && hasData.transitioning) return + actives.collapse('hide') + hasData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing') + [dimension](0) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in') + [dimension]('auto') + this.transitioning = 0 + this.$element.trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + [dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element + [dimension](this.$element[dimension]()) + [0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse') + .removeClass('in') + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .trigger('hidden.bs.collapse') + .removeClass('collapsing') + .addClass('collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one($.support.transition.end, $.proxy(complete, this)) + .emulateTransitionEnd(350) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + var old = $.fn.collapse + + $.fn.collapse = function (option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && option == 'show') option = !option + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) { + var $this = $(this), href + var target = $this.attr('data-target') + || e.preventDefault() + || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7 + var $target = $(target) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $this.data() + var parent = $this.attr('data-parent') + var $parent = parent && $(parent) + + if (!data || !data.transitioning) { + if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed') + $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed') + } + + $target.collapse(option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.1.1 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle=dropdown]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $('