pull/67/head
Michael Joseph Walsh 2012-05-08 19:52:13 -04:00
parent e6f77fd061
commit 755d0d4c88
47 changed files with 17674 additions and 81 deletions

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java"/>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources"/>
<classpathentry kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

42
account-chooser/.project Normal file
View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>account-chooser</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.common.project.facet.core.builder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/webapp"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
<attributes>
<attribute name="hide" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
<classpathentry kind="output" path=""/>
</classpath>

View File

@ -0,0 +1,9 @@
#Mon May 07 14:38:49 EDT 2012
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6

View File

@ -0,0 +1,5 @@
#Mon May 07 14:38:46 EDT 2012
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-modules id="moduleCoreId" project-version="1.5.0">
<wb-module deploy-name="account-chooser">
<wb-resource deploy-path="/" source-path="/target/m2e-wtp/web-resources"/>
<wb-resource deploy-path="/" source-path="/src/main/webapp" tag="defaultRootSource"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/resources"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/main/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/java"/>
<wb-resource deploy-path="/WEB-INF/classes" source-path="/src/test/resources"/>
<property name="context-root" value="account-chooser"/>
<property name="java-output-path" value="/account-chooser/target/classes"/>
</wb-module>
</project-modules>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<faceted-project>
<fixed facet="wst.jsdt.web"/>
<installed facet="java" version="1.6"/>
<installed facet="wst.jsdt.web" version="1.0"/>
<installed facet="jst.web" version="2.5"/>
</faceted-project>

View File

@ -0,0 +1 @@
org.eclipse.wst.jsdt.launching.baseBrowserLibrary

View File

@ -0,0 +1 @@
Window

53
account-chooser/README.md Normal file
View File

@ -0,0 +1,53 @@
# Account Choooser UI Application
## Overview
This is Web application created in response to [Issue #39] to permit the Client AuthenticationFilter to speak to multiple OpenID Connect servers.
## Configuration
Configure a bean configuration to the spring-servlet.xml like so:
<bean class="org.mitre.account_chooser.OIDCServers">
<property name="servers">
<map>
<entry key="1">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 1" />
</bean>
</entry>
<entry key="2">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 2" />
</bean>
</entry>
<entry key="3">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 3" />
</bean>
</entry>
</map>
</property>
</bean>
The keys must match those found in the OpenIdConnectAuthenticationFilter's configuration like so:
<bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter">
<property name="OIDCServers">
<map>
<entry key="1">
<property name="authorizationEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/openidconnect/auth" />
<property name="tokenEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/checkid" />
<property name="checkIDEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/checkid" />
<property name="clientId"
value="someClientId" />
<property name="clientSecret" value="someClientSecret" />
</entry>
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

103
account-chooser/pom.xml Normal file
View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.mitre</groupId>
<artifactId>account-chooser</artifactId>
<version>0.1-SNAPSHOT</version>
<name>Account Chooser UI</name>
<packaging>war</packaging>
<parent>
<groupId>org.mitre</groupId>
<artifactId>openid-connect-parent</artifactId>
<version>0.1-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<dependencies>
</dependencies>
<description>A Spring MVC Web Application written in response to Issue #39.
</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java-version}</source>
<target>${java-version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<executions>
<execution>
<id>prepare-war</id>
<phase>prepare-package</phase>
<goals>
<goal>exploded</goal>
</goals>
</execution>
</executions>
<configuration>
<warName>account-chooser</warName>
<useCache>true</useCache>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>install</id>
<phase>install</phase>
<goals>
<goal>sources</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<junitArtifactName>junit:junit</junitArtifactName>
<excludes>
<exclude>**/*_Roo_*</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.0-beta-1</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.4</version>
<configuration>
<formats>
<format>xml</format>
</formats>
<instrumentation>
<ignores>
<ignore>org.apache.log4j.*</ignore>
</ignores>
<excludes>
</excludes>
</instrumentation>
</configuration>
<executions>
<execution>
<id>cobertura</id>
<phase>package</phase>
<goals>
<goal>cobertura</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,89 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.account_chooser;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
/**
* Account Chooser UI application
*
* @author nemonik
*
* See README.md for configuration.
*
*/
@Controller
public class AccountChooserController {
@Autowired
OIDCServers servers;
private static Log logger = LogFactory
.getLog(AccountChooserController.class);
/**
* Handles request to choose an Account
*
* @param redirectUri
* A redirection URI where the response will be sent
* @return
*/
@RequestMapping(value = "/", method = { RequestMethod.GET,
RequestMethod.POST })
public ModelAndView handleChooserRequest(
@RequestParam("redirect_uri") String redirectUri) {
ModelAndView modelAndView = new ModelAndView("form");
modelAndView.addObject("servers", servers);
modelAndView.addObject("redirect_uri", redirectUri);
modelAndView.setViewName("chooser");
return modelAndView;
}
/**
* Handles form submits
*
* @param redirectUri
* A redirection URI where the response will be sent.
* @param alias
* The OIDC alias selected.
* @param response
* Provide the HTTP-specific functionality for sending a
* response. In this case a redirect to redirect the End-User
* back to the OpenID Connect Client.
* @throws IOException
* If an output exception occurs in sending the redirect.
*/
@RequestMapping(value = "/selected")
public void processSubmit(@RequestParam("redirect_uri") String redirectUri,
@RequestParam("alias") String alias, HttpServletResponse response)
throws IOException {
response.sendRedirect(redirectUri + "?oidc_alias=" + alias);
}
}

View File

@ -0,0 +1,39 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.account_chooser;
/**
* @author nemonik
*
*/
public class OIDCServer {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "OIDCServer [name=" + name + "]";
}
}

View File

@ -0,0 +1,77 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.account_chooser;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
/**
* @author nemonik
*
*/
public class OIDCServers implements InitializingBean {
private Map<String, ? extends OIDCServer> servers = new HashMap<String, OIDCServer>();
private static Log logger = LogFactory.getLog(OIDCServers.class);
/*
* (non-Javadoc)
*
* @see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
// used for debugging...
if (!servers.isEmpty()) {
logger.info(this.toString());
}
}
/**
* Return the OIDCServers associated with this
*
* @return
*/
public Map<String, ? extends OIDCServer> getServers() {
return servers;
}
/**
* Set the OIDCServers associated with this
*
* @param signers
* List of JwtSigners to associate with this service
*/
public void setServers(Map<String, ? extends OIDCServer> servers) {
this.servers = servers;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "OIDCServers [servers=" + servers
+ "]";
}
}

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<!-- Scan for components -->
<context:component-scan annotation-config="true" base-package="org.mitre.account_chooser"/>
<!-- Enables the Spring MVC @Controller programming model -->
<tx:annotation-driven transaction-manager="transactionManager" />
<mvc:annotation-driven />
<mvc:default-servlet-handler />
<!-- View configuration -->
<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<mvc:resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
<property name="order" value="2"/>
</bean>
<!-- End view configuration -->
<bean class="org.mitre.account_chooser.OIDCServers">
<property name="servers">
<map>
<entry key="1">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 1" />
</bean>
</entry>
<entry key="2">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 2" />
</bean>
</entry>
<entry key="3">
<bean class="org.mitre.account_chooser.OIDCServer">
<property name="name" value="OIDC Server 3" />
</bean>
</entry>
</map>
</property>
</bean>
</beans>

View File

@ -0,0 +1,103 @@
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@page import="org.mitre.account_chooser.OIDCServer"%>
<%@page import="java.util.Map"%>
<%@page import="java.util.Iterator"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<jsp:useBean id="servers" type="org.mitre.account_chooser.OIDCServers"
scope="request" />
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Account Chooser</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Account Chooser GUI">
<meta name="author" content="nemonik">
<link href="resources/bootstrap/css/bootstrap.css" rel="stylesheet">
<link href="resources/bootstrap/css/bootstrap-responsive.css"
rel="stylesheet">
<link href="resources/bootstrap/css/docs.css" rel="stylesheet">
<style>
body {
padding-top: 60px;
/* 60px to make the container go all the way to the bottom of the topbar */
}
</style>
<!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="row">
<div class="span12">
<form class="form-horizontal" action="selected" method="get">
<div class="control-group">
<label class="control-label" for="select01">Account:</label>
<div class="controls">
<select name="alias">
<%
Map map = servers.getServers();
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
String alias = (String) entry.getKey();
OIDCServer server = (OIDCServer) entry.getValue();
%>
<option value="<%= alias %>"><%= server.getName() %></option>
<%
}
%>
</select>
<p class="help-block">Select the Account you'd like to authenticate with.</p>
</div>
</div>
<div class="control-group">
<div class="controls">
<input name="redirect_uri" type="hidden" value="<c:out value="${redirect_uri}"/>">
</div>
</div>
<div class="form-actions">
<button class="btn btn-primary" type="submit">Submit</button>
<button class="btn">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<!--/container-->
<!-- Placed at the end of the document so the pages load faster -->
<script src="resources/bootstrap/js/jquery.js"></script>
<script src="resources/bootstrap/js/bootstrap-transition.js"></script>
<script src="resources/bootstrap/js/bootstrap-alert.js"></script>
<script src="resources/bootstrap/js/bootstrap-modal.js"></script>
<script src="resources/bootstrap/js/bootstrap-dropdown.js"></script>
<script src="resources/bootstrap/js/bootstrap-scrollspy.js"></script>
<script src="resources/bootstrap/js/bootstrap-tab.js"></script>
<script src="resources/bootstrap/js/bootstrap-tooltip.js"></script>
<script src="resources/bootstrap/js/bootstrap-popover.js"></script>
<script src="resources/bootstrap/js/bootstrap-button.js"></script>
<script src="resources/bootstrap/js/bootstrap-collapse.js"></script>
<script src="resources/bootstrap/js/bootstrap-carousel.js"></script>
<script src="resources/bootstrap/js/bootstrap-typeahead.js"></script>
<script type="text/javascript" language="JavaScript">
// <![CDATA [
// javascript secific to the page would go here, if I had any...
// ]]>
</script>
</body>
</html>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<trim-directive-whitespaces>true</trim-directive-whitespaces>
</jsp-property-group>
</jsp-config>
</web-app>

View File

@ -0,0 +1,686 @@
/*!
* Bootstrap Responsive v2.0.2
*
* Copyright 2012 Twitter, Inc
* Licensed under the Apache License v2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Designed and built with all the love in the world @twitter by @mdo and @fat.
*/
.clearfix {
*zoom: 1;
}
.clearfix:before,
.clearfix:after {
display: table;
content: "";
}
.clearfix:after {
clear: both;
}
.hide-text {
overflow: hidden;
text-indent: 100%;
white-space: nowrap;
}
.input-block-level {
display: block;
width: 100%;
min-height: 28px;
/* Make inputs at least the height of their button counterpart */
/* Makes inputs behave like true block-level elements */
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.hidden {
display: none;
visibility: hidden;
}
.visible-phone {
display: none;
}
.visible-tablet {
display: none;
}
.visible-desktop {
display: block;
}
.hidden-phone {
display: block;
}
.hidden-tablet {
display: block;
}
.hidden-desktop {
display: none;
}
@media (max-width: 767px) {
.visible-phone {
display: block;
}
.hidden-phone {
display: none;
}
.hidden-desktop {
display: block;
}
.visible-desktop {
display: none;
}
}
@media (min-width: 768px) and (max-width: 979px) {
.visible-tablet {
display: block;
}
.hidden-tablet {
display: none;
}
.hidden-desktop {
display: block;
}
.visible-desktop {
display: none;
}
}
@media (max-width: 480px) {
.nav-collapse {
-webkit-transform: translate3d(0, 0, 0);
}
.page-header h1 small {
display: block;
line-height: 18px;
}
input[type="checkbox"],
input[type="radio"] {
border: 1px solid #ccc;
}
.form-horizontal .control-group > label {
float: none;
width: auto;
padding-top: 0;
text-align: left;
}
.form-horizontal .controls {
margin-left: 0;
}
.form-horizontal .control-list {
padding-top: 0;
}
.form-horizontal .form-actions {
padding-left: 10px;
padding-right: 10px;
}
.modal {
position: absolute;
top: 10px;
left: 10px;
right: 10px;
width: auto;
margin: 0;
}
.modal.fade.in {
top: auto;
}
.modal-header .close {
padding: 10px;
margin: -10px;
}
.carousel-caption {
position: static;
}
}
@media (max-width: 767px) {
body {
padding-left: 20px;
padding-right: 20px;
}
.navbar-fixed-top {
margin-left: -20px;
margin-right: -20px;
}
.container {
width: auto;
}
.row-fluid {
width: 100%;
}
.row {
margin-left: 0;
}
.row > [class*="span"],
.row-fluid > [class*="span"] {
float: none;
display: block;
width: auto;
margin: 0;
}
.thumbnails [class*="span"] {
width: auto;
}
input[class*="span"],
select[class*="span"],
textarea[class*="span"],
.uneditable-input {
display: block;
width: 100%;
min-height: 28px;
/* Make inputs at least the height of their button counterpart */
/* Makes inputs behave like true block-level elements */
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.input-prepend input[class*="span"],
.input-append input[class*="span"] {
width: auto;
}
}
@media (min-width: 768px) and (max-width: 979px) {
.row {
margin-left: -20px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
margin-left: 20px;
}
.container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 724px;
}
.span12 {
width: 724px;
}
.span11 {
width: 662px;
}
.span10 {
width: 600px;
}
.span9 {
width: 538px;
}
.span8 {
width: 476px;
}
.span7 {
width: 414px;
}
.span6 {
width: 352px;
}
.span5 {
width: 290px;
}
.span4 {
width: 228px;
}
.span3 {
width: 166px;
}
.span2 {
width: 104px;
}
.span1 {
width: 42px;
}
.offset12 {
margin-left: 764px;
}
.offset11 {
margin-left: 702px;
}
.offset10 {
margin-left: 640px;
}
.offset9 {
margin-left: 578px;
}
.offset8 {
margin-left: 516px;
}
.offset7 {
margin-left: 454px;
}
.offset6 {
margin-left: 392px;
}
.offset5 {
margin-left: 330px;
}
.offset4 {
margin-left: 268px;
}
.offset3 {
margin-left: 206px;
}
.offset2 {
margin-left: 144px;
}
.offset1 {
margin-left: 82px;
}
.row-fluid {
width: 100%;
*zoom: 1;
}
.row-fluid:before,
.row-fluid:after {
display: table;
content: "";
}
.row-fluid:after {
clear: both;
}
.row-fluid > [class*="span"] {
float: left;
margin-left: 2.762430939%;
}
.row-fluid > [class*="span"]:first-child {
margin-left: 0;
}
.row-fluid > .span12 {
width: 99.999999993%;
}
.row-fluid > .span11 {
width: 91.436464082%;
}
.row-fluid > .span10 {
width: 82.87292817100001%;
}
.row-fluid > .span9 {
width: 74.30939226%;
}
.row-fluid > .span8 {
width: 65.74585634900001%;
}
.row-fluid > .span7 {
width: 57.182320438000005%;
}
.row-fluid > .span6 {
width: 48.618784527%;
}
.row-fluid > .span5 {
width: 40.055248616%;
}
.row-fluid > .span4 {
width: 31.491712705%;
}
.row-fluid > .span3 {
width: 22.928176794%;
}
.row-fluid > .span2 {
width: 14.364640883%;
}
.row-fluid > .span1 {
width: 5.801104972%;
}
input,
textarea,
.uneditable-input {
margin-left: 0;
}
input.span12, textarea.span12, .uneditable-input.span12 {
width: 714px;
}
input.span11, textarea.span11, .uneditable-input.span11 {
width: 652px;
}
input.span10, textarea.span10, .uneditable-input.span10 {
width: 590px;
}
input.span9, textarea.span9, .uneditable-input.span9 {
width: 528px;
}
input.span8, textarea.span8, .uneditable-input.span8 {
width: 466px;
}
input.span7, textarea.span7, .uneditable-input.span7 {
width: 404px;
}
input.span6, textarea.span6, .uneditable-input.span6 {
width: 342px;
}
input.span5, textarea.span5, .uneditable-input.span5 {
width: 280px;
}
input.span4, textarea.span4, .uneditable-input.span4 {
width: 218px;
}
input.span3, textarea.span3, .uneditable-input.span3 {
width: 156px;
}
input.span2, textarea.span2, .uneditable-input.span2 {
width: 94px;
}
input.span1, textarea.span1, .uneditable-input.span1 {
width: 32px;
}
}
@media (max-width: 979px) {
body {
padding-top: 0;
}
.navbar-fixed-top {
position: static;
margin-bottom: 18px;
}
.navbar-fixed-top .navbar-inner {
padding: 5px;
}
.navbar .container {
width: auto;
padding: 0;
}
.navbar .brand {
padding-left: 10px;
padding-right: 10px;
margin: 0 0 0 -5px;
}
.navbar .nav-collapse {
clear: left;
}
.navbar .nav {
float: none;
margin: 0 0 9px;
}
.navbar .nav > li {
float: none;
}
.navbar .nav > li > a {
margin-bottom: 2px;
}
.navbar .nav > .divider-vertical {
display: none;
}
.navbar .nav .nav-header {
color: #999999;
text-shadow: none;
}
.navbar .nav > li > a,
.navbar .dropdown-menu a {
padding: 6px 15px;
font-weight: bold;
color: #999999;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.navbar .dropdown-menu li + li a {
margin-bottom: 2px;
}
.navbar .nav > li > a:hover,
.navbar .dropdown-menu a:hover {
background-color: #222222;
}
.navbar .dropdown-menu {
position: static;
top: auto;
left: auto;
float: none;
display: block;
max-width: none;
margin: 0 15px;
padding: 0;
background-color: transparent;
border: none;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.navbar .dropdown-menu:before,
.navbar .dropdown-menu:after {
display: none;
}
.navbar .dropdown-menu .divider {
display: none;
}
.navbar-form,
.navbar-search {
float: none;
padding: 9px 15px;
margin: 9px 0;
border-top: 1px solid #222222;
border-bottom: 1px solid #222222;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
-moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
}
.navbar .nav.pull-right {
float: none;
margin-left: 0;
}
.navbar-static .navbar-inner {
padding-left: 10px;
padding-right: 10px;
}
.btn-navbar {
display: block;
}
.nav-collapse {
overflow: hidden;
height: 0;
}
}
@media (min-width: 980px) {
.nav-collapse.collapse {
height: auto !important;
overflow: visible !important;
}
}
@media (min-width: 1200px) {
.row {
margin-left: -30px;
*zoom: 1;
}
.row:before,
.row:after {
display: table;
content: "";
}
.row:after {
clear: both;
}
[class*="span"] {
float: left;
margin-left: 30px;
}
.container,
.navbar-fixed-top .container,
.navbar-fixed-bottom .container {
width: 1170px;
}
.span12 {
width: 1170px;
}
.span11 {
width: 1070px;
}
.span10 {
width: 970px;
}
.span9 {
width: 870px;
}
.span8 {
width: 770px;
}
.span7 {
width: 670px;
}
.span6 {
width: 570px;
}
.span5 {
width: 470px;
}
.span4 {
width: 370px;
}
.span3 {
width: 270px;
}
.span2 {
width: 170px;
}
.span1 {
width: 70px;
}
.offset12 {
margin-left: 1230px;
}
.offset11 {
margin-left: 1130px;
}
.offset10 {
margin-left: 1030px;
}
.offset9 {
margin-left: 930px;
}
.offset8 {
margin-left: 830px;
}
.offset7 {
margin-left: 730px;
}
.offset6 {
margin-left: 630px;
}
.offset5 {
margin-left: 530px;
}
.offset4 {
margin-left: 430px;
}
.offset3 {
margin-left: 330px;
}
.offset2 {
margin-left: 230px;
}
.offset1 {
margin-left: 130px;
}
.row-fluid {
width: 100%;
*zoom: 1;
}
.row-fluid:before,
.row-fluid:after {
display: table;
content: "";
}
.row-fluid:after {
clear: both;
}
.row-fluid > [class*="span"] {
float: left;
margin-left: 2.564102564%;
}
.row-fluid > [class*="span"]:first-child {
margin-left: 0;
}
.row-fluid > .span12 {
width: 100%;
}
.row-fluid > .span11 {
width: 91.45299145300001%;
}
.row-fluid > .span10 {
width: 82.905982906%;
}
.row-fluid > .span9 {
width: 74.358974359%;
}
.row-fluid > .span8 {
width: 65.81196581200001%;
}
.row-fluid > .span7 {
width: 57.264957265%;
}
.row-fluid > .span6 {
width: 48.717948718%;
}
.row-fluid > .span5 {
width: 40.170940171000005%;
}
.row-fluid > .span4 {
width: 31.623931624%;
}
.row-fluid > .span3 {
width: 23.076923077%;
}
.row-fluid > .span2 {
width: 14.529914530000001%;
}
.row-fluid > .span1 {
width: 5.982905983%;
}
input,
textarea,
.uneditable-input {
margin-left: 0;
}
input.span12, textarea.span12, .uneditable-input.span12 {
width: 1160px;
}
input.span11, textarea.span11, .uneditable-input.span11 {
width: 1060px;
}
input.span10, textarea.span10, .uneditable-input.span10 {
width: 960px;
}
input.span9, textarea.span9, .uneditable-input.span9 {
width: 860px;
}
input.span8, textarea.span8, .uneditable-input.span8 {
width: 760px;
}
input.span7, textarea.span7, .uneditable-input.span7 {
width: 660px;
}
input.span6, textarea.span6, .uneditable-input.span6 {
width: 560px;
}
input.span5, textarea.span5, .uneditable-input.span5 {
width: 460px;
}
input.span4, textarea.span4, .uneditable-input.span4 {
width: 360px;
}
input.span3, textarea.span3, .uneditable-input.span3 {
width: 260px;
}
input.span2, textarea.span2, .uneditable-input.span2 {
width: 160px;
}
input.span1, textarea.span1, .uneditable-input.span1 {
width: 60px;
}
.thumbnails {
margin-left: -30px;
}
.thumbnails > li {
margin-left: 30px;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,845 @@
/* Add additional stylesheets below
-------------------------------------------------- */
/*
Bootstrap's documentation styles
Special styles for presenting Bootstrap's documentation and examples
*/
/* Body and structure
-------------------------------------------------- */
body {
position: relative;
padding-top: 90px;
background-color: #fff;
background-image: url(../img/grid-18px-masked.png);
background-repeat: repeat-x;
background-position: 0 40px;
}
/* Tweak navbar brand link to be super sleek
-------------------------------------------------- */
.navbar-fixed-top .brand {
padding-right: 0;
padding-left: 0;
margin-left: 20px;
float: right;
font-weight: bold;
color: #000;
text-shadow: 0 1px 0 rgba(255,255,255,.1), 0 0 30px rgba(255,255,255,.125);
-webkit-transition: all .2s linear;
-moz-transition: all .2s linear;
transition: all .2s linear;
}
.navbar-fixed-top .brand:hover {
text-decoration: none;
}
/* Space out sub-sections more
-------------------------------------------------- */
section {
padding-top: 60px;
}
/* Faded out hr */
hr.soften {
height: 1px;
margin: 54px 0;
background-image: -webkit-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
background-image: -moz-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
background-image: -ms-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
background-image: -o-linear-gradient(left, rgba(0,0,0,0), rgba(0,0,0,.1), rgba(0,0,0,0));
border: 0;
}
/* Jumbotrons
-------------------------------------------------- */
.jumbotron {
position: relative;
}
.jumbotron h1 {
margin-bottom: 9px;
font-size: 81px;
font-weight: bold;
letter-spacing: -1px;
line-height: 1;
}
.jumbotron p {
margin-bottom: 18px;
font-weight: 300;
}
.jumbotron .btn-large {
font-size: 20px;
font-weight: normal;
padding: 14px 24px;
margin-right: 10px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
.jumbotron .btn-large small {
font-size: 14px;
}
/* Masthead (docs home) */
.masthead {
padding-top: 36px;
margin-bottom: 72px;
}
.masthead h1,
.masthead p {
text-align: center;
}
.masthead h1 {
margin-bottom: 18px;
}
.masthead p {
margin-left: 5%;
margin-right: 5%;
font-size: 30px;
line-height: 36px;
}
/* Specific jumbotrons
------------------------- */
/* supporting docs pages */
.subhead {
padding-bottom: 0;
margin-bottom: 9px;
}
.subhead h1 {
font-size: 54px;
}
/* Subnav */
.subnav {
width: 100%;
height: 36px;
background-color: #eeeeee; /* Old browsers */
background-repeat: repeat-x; /* Repeat the gradient */
background-image: -moz-linear-gradient(top, #f5f5f5 0%, #eeeeee 100%); /* FF3.6+ */
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f5f5), color-stop(100%,#eeeeee)); /* Chrome,Safari4+ */
background-image: -webkit-linear-gradient(top, #f5f5f5 0%,#eeeeee 100%); /* Chrome 10+,Safari 5.1+ */
background-image: -ms-linear-gradient(top, #f5f5f5 0%,#eeeeee 100%); /* IE10+ */
background-image: -o-linear-gradient(top, #f5f5f5 0%,#eeeeee 100%); /* Opera 11.10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f5f5', endColorstr='#eeeeee',GradientType=0 ); /* IE6-9 */
background-image: linear-gradient(top, #f5f5f5 0%,#eeeeee 100%); /* W3C */
border: 1px solid #e5e5e5;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.subnav .nav {
margin-bottom: 0;
}
.subnav .nav > li > a {
margin: 0;
padding-top: 11px;
padding-bottom: 11px;
border-left: 1px solid #f5f5f5;
border-right: 1px solid #e5e5e5;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.subnav .nav > .active > a,
.subnav .nav > .active > a:hover {
padding-left: 13px;
color: #777;
background-color: #e9e9e9;
border-right-color: #ddd;
border-left: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0,0,0,.05);
-moz-box-shadow: inset 0 3px 5px rgba(0,0,0,.05);
box-shadow: inset 0 3px 5px rgba(0,0,0,.05);
}
.subnav .nav > .active > a .caret,
.subnav .nav > .active > a:hover .caret {
border-top-color: #777;
}
.subnav .nav > li:first-child > a,
.subnav .nav > li:first-child > a:hover {
border-left: 0;
padding-left: 12px;
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.subnav .nav > li:last-child > a {
border-right: 0;
}
.subnav .dropdown-menu {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius: 0 0 4px 4px;
border-radius: 0 0 4px 4px;
}
/* Fixed subnav on scroll, but only for 980px and up (sorry IE!) */
@media (min-width: 980px) {
.subnav-fixed {
position: fixed;
top: 40px;
left: 0;
right: 0;
z-index: 1020; /* 10 less than .navbar-fixed to prevent any overlap */
border-color: #d5d5d5;
border-width: 0 0 1px; /* drop the border on the fixed edges */
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
-webkit-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
-moz-box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
box-shadow: inset 0 1px 0 #fff, 0 1px 5px rgba(0,0,0,.1);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); /* IE6-9 */
}
.subnav-fixed .nav {
width: 938px;
margin: 0 auto;
padding: 0 1px;
}
.subnav .nav > li:first-child > a,
.subnav .nav > li:first-child > a:hover {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
}
/* Quick links
-------------------------------------------------- */
.bs-links {
margin: 36px 0;
}
.quick-links {
min-height: 30px;
margin: 0;
padding: 5px 20px;
list-style: none;
text-align: center;
overflow: hidden;
}
.quick-links:first-child {
min-height: 0;
}
.quick-links li {
display: inline;
margin: 0 5px;
color: #999;
}
.quick-links .github-btn,
.quick-links .tweet-btn,
.quick-links .follow-btn {
position: relative;
top: 5px;
}
/* Marketing section of Overview
-------------------------------------------------- */
.marketing .row {
margin-bottom: 9px;
}
.marketing h1 {
margin: 36px 0 27px;
font-size: 40px;
font-weight: 300;
text-align: center;
}
.marketing h2,
.marketing h3 {
font-weight: 300;
}
.marketing h2 {
font-size: 22px;
}
.marketing p {
margin-right: 10px;
}
.marketing .bs-icon {
float: left;
margin: 7px 10px 0 0;
opacity: .8;
}
.marketing .small-bs-icon {
float: left;
margin: 4px 5px 0 0;
}
/* Footer
-------------------------------------------------- */
.footer {
margin-top: 45px;
padding: 35px 0 36px;
border-top: 1px solid #e5e5e5;
}
.footer p {
margin-bottom: 0;
color: #555;
}
/* Special grid styles
-------------------------------------------------- */
.show-grid {
margin-top: 10px;
margin-bottom: 20px;
}
.show-grid [class*="span"] {
background-color: #eee;
text-align: center;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
min-height: 30px;
line-height: 30px;
}
.show-grid:hover [class*="span"] {
background: #ddd;
}
.show-grid .show-grid {
margin-top: 0;
margin-bottom: 0;
}
.show-grid .show-grid [class*="span"] {
background-color: #ccc;
}
/* Render mini layout previews
-------------------------------------------------- */
.mini-layout {
border: 1px solid #ddd;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,.075);
-moz-box-shadow: 0 1px 2px rgba(0,0,0,.075);
box-shadow: 0 1px 2px rgba(0,0,0,.075);
}
.mini-layout {
height: 240px;
margin-bottom: 20px;
padding: 9px;
}
.mini-layout div {
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.mini-layout .mini-layout-body {
background-color: #dceaf4;
margin: 0 auto;
width: 70%;
height: 240px;
}
.mini-layout.fluid .mini-layout-sidebar,
.mini-layout.fluid .mini-layout-header,
.mini-layout.fluid .mini-layout-body {
float: left;
}
.mini-layout.fluid .mini-layout-sidebar {
background-color: #bbd8e9;
width: 20%;
height: 240px;
}
.mini-layout.fluid .mini-layout-body {
width: 77.5%;
margin-left: 2.5%;
}
/* Popover docs
-------------------------------------------------- */
.popover-well {
min-height: 160px;
}
.popover-well .popover {
display: block;
}
.popover-well .popover-wrapper {
width: 50%;
height: 160px;
float: left;
margin-left: 55px;
position: relative;
}
.popover-well .popover-menu-wrapper {
height: 80px;
}
.large-bird {
margin: 5px 0 0 310px;
opacity: .1;
}
/* Download page
-------------------------------------------------- */
.download .page-header {
margin-top: 36px;
}
.page-header .toggle-all {
margin-top: 5px;
}
/* Space out h3s when following a section */
.download h3 {
margin-bottom: 5px;
}
.download-builder input + h3,
.download-builder .checkbox + h3 {
margin-top: 9px;
}
/* Fields for variables */
.download-builder input[type=text] {
margin-bottom: 9px;
font-family: Menlo, Monaco, "Courier New", monospace;
font-size: 12px;
color: #d14;
}
.download-builder input[type=text]:focus {
background-color: #fff;
}
/* Custom, larger checkbox labels */
.download .checkbox {
padding: 6px 10px 6px 25px;
color: #555;
background-color: #f9f9f9;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
cursor: pointer;
}
.download .checkbox:hover {
color: #333;
background-color: #f5f5f5;
}
.download .checkbox small {
font-size: 12px;
color: #777;
}
/* Variables section */
#variables label {
margin-bottom: 0;
}
/* Giant download button */
.download-btn {
margin: 36px 0 108px;
}
#download p,
#download h4 {
max-width: 50%;
margin: 0 auto;
color: #999;
text-align: center;
}
#download h4 {
margin-bottom: 0;
}
#download p {
margin-bottom: 18px;
}
.download-btn .btn {
display: block;
width: auto;
padding: 19px 24px;
margin-bottom: 27px;
font-size: 30px;
line-height: 1;
text-align: center;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
}
/* Color swatches on LESS docs page
-------------------------------------------------- */
/* Sets the width of the td */
.swatch-col {
width: 30px;
}
/* Le swatch */
.swatch {
display: inline-block;
width: 30px;
height: 20px;
margin: -6px 0;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
/* For white swatches, give a border */
.swatch-bordered {
width: 28px;
height: 18px;
border: 1px solid #eee;
}
/* Misc
-------------------------------------------------- */
img {
max-width: 100%;
}
/* Make tables spaced out a bit more */
h2 + table,
h3 + table,
h4 + table,
h2 + .row {
margin-top: 5px;
}
/* Example sites showcase */
.example-sites img {
max-width: 100%;
margin: 0 auto;
}
.marketing-byline {
margin: -18px 0 27px;
font-size: 18px;
font-weight: 300;
line-height: 24px;
color: #999;
text-align: center;
}
.scrollspy-example {
height: 200px;
overflow: auto;
position: relative;
}
/* Remove bottom margin on example forms in wells */
form.well {
padding: 14px;
}
/* Tighten up spacing */
.well hr {
margin: 18px 0;
}
/* Fake the :focus state to demo it */
.focused {
border-color: rgba(82,168,236,.8);
-webkit-box-shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
-moz-box-shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
box-shadow: inset 0 1px 3px rgba(0,0,0,.1), 0 0 8px rgba(82,168,236,.6);
outline: 0;
}
/* For input sizes, make them display block */
.docs-input-sizes select,
.docs-input-sizes input[type=text] {
display: block;
margin-bottom: 9px;
}
/* Icons
------------------------- */
.the-icons {
margin-left: 0;
list-style: none;
}
.the-icons i:hover {
background-color: rgba(255,0,0,.25);
}
/* Eaxmples page
------------------------- */
.bootstrap-examples .thumbnail {
margin-bottom: 9px;
background-color: #fff;
}
/* Responsive table
------------------------- */
.responsive-utilities th small {
display: block;
font-weight: normal;
color: #999;
}
.responsive-utilities tbody th {
font-weight: normal;
}
.responsive-utilities td {
text-align: center;
}
.responsive-utilities td.is-visible {
color: #468847;
background-color: #dff0d8 !important;
}
.responsive-utilities td.is-hidden {
color: #ccc;
background-color: #f9f9f9 !important;
}
/* Responsive tests
------------------------- */
.responsive-utilities-test {
margin-top: 5px;
margin-left: 0;
list-style: none;
overflow: hidden; /* clear floats */
}
.responsive-utilities-test li {
position: relative;
float: left;
width: 25%;
height: 43px;
font-size: 14px;
font-weight: bold;
line-height: 43px;
color: #999;
text-align: center;
border: 1px solid #ddd;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.responsive-utilities-test li + li {
margin-left: 10px;
}
.responsive-utilities-test span {
position: absolute;
top: -1px;
left: -1px;
right: -1px;
bottom: -1px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.responsive-utilities-test span {
color: #468847;
background-color: #dff0d8;
border: 1px solid #d6e9c6;
}
/* Responsive Docs
-------------------------------------------------- */
@media (max-width: 480px) {
/* Reduce padding above jumbotron */
body {
padding-top: 70px;
}
/* Change up some type stuff */
h2 {
margin-top: 27px;
}
h2 small {
display: block;
line-height: 18px;
}
h3 {
margin-top: 18px;
}
/* Adjust the jumbotron */
.jumbotron h1,
.jumbotron p {
text-align: center;
margin-right: 0;
}
.jumbotron h1 {
font-size: 45px;
margin-right: 0;
}
.jumbotron p {
margin-right: 0;
margin-left: 0;
font-size: 18px;
line-height: 24px;
}
.jumbotron .btn {
display: block;
font-size: 18px;
padding: 10px 14px;
margin: 0 auto 10px;
}
/* Masthead (home page jumbotron) */
.masthead {
padding-top: 0;
}
/* Don't space out quick links so much */
.quick-links {
margin: 40px 0 0;
}
/* hide the bullets on mobile since our horizontal space is limited */
.quick-links .divider {
display: none;
}
/* center example sites */
.example-sites {
margin-left: 0;
}
.example-sites > li {
float: none;
display: block;
max-width: 280px;
margin: 0 auto 18px;
text-align: center;
}
.example-sites .thumbnail > img {
max-width: 270px;
}
table code {
white-space: normal;
word-wrap: break-word;
word-break: break-all;
}
/* Modal example */
.modal-example .modal {
position: relative;
top: auto;
right: auto;
bottom: auto;
left: auto;
}
}
@media (max-width: 768px) {
/* Remove any padding from the body */
body {
padding-top: 0;
}
/* Jumbotron buttons */
.jumbotron .btn {
margin-bottom: 10px;
}
/* Subnav */
.subnav {
position: static;
top: auto;
z-index: auto;
width: auto;
height: auto;
background: #fff; /* whole background property since we use a background-image for gradient */
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
.subnav .nav > li {
float: none;
}
.subnav .nav > li > a {
border: 0;
}
.subnav .nav > li + li > a {
border-top: 1px solid #e5e5e5;
}
.subnav .nav > li:first-child > a,
.subnav .nav > li:first-child > a:hover {
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
}
/* Popovers */
.large-bird {
display: none;
}
.popover-well .popover-wrapper {
margin-left: 0;
}
/* Space out the show-grid examples */
.show-grid [class*="span"] {
margin-bottom: 5px;
}
/* Unfloat the back to top link in footer */
.footer .pull-right {
float: none;
}
.footer p {
margin-bottom: 9px;
}
}
@media (min-width: 480px) and (max-width: 768px) {
/* Scale down the jumbotron content */
.jumbotron h1 {
font-size: 54px;
}
.jumbotron p {
margin-right: 0;
margin-left: 0;
}
}
@media (min-width: 768px) and (max-width: 980px) {
/* Remove any padding from the body */
body {
padding-top: 0;
}
/* Scale down the jumbotron content */
.jumbotron h1 {
font-size: 72px;
}
}
@media (max-width: 980px) {
/* Unfloat brand */
.navbar-fixed-top .brand {
float: left;
margin-left: 0;
padding-left: 10px;
padding-right: 10px;
}
/* Inline-block quick links for more spacing */
.quick-links li {
display: inline-block;
margin: 5px;
}
}
/* LARGE DESKTOP SCREENS */
@media (min-width: 1210px) {
/* Update subnav container */
.subnav-fixed .nav {
width: 1168px; /* 2px less to account for left/right borders being removed when in fixed mode */
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

View File

@ -0,0 +1,106 @@
## 2.0 BOOTSTRAP JS PHILOSOPHY
These are the high-level design rules which guide the development of Bootstrap's plugin apis.
---
### DATA-ATTRIBUTE API
We believe you should be able to use all plugins provided by Bootstrap purely through the markup API without writing a single line of javascript.
We acknowledge that this isn't always the most performant and sometimes it may be desirable to turn this functionality off altogether. Therefore, as of 2.0 we provide the ability to disable the data attribute API by unbinding all events on the body namespaced with `'data-api'`. This looks like this:
$('body').off('.data-api')
To target a specific plugin, just include the plugins name as a namespace along with the data-api namespace like this:
$('body').off('.alert.data-api')
---
### PROGRAMATIC API
We also believe you should be able to use all plugins provided by Bootstrap purely through the JS API.
All public APIs should be single, chainable methods, and return the collection acted upon.
$(".btn.danger").button("toggle").addClass("fat")
All methods should accept an optional options object, a string which targets a particular method, or null which initiates the default behavior:
$("#myModal").modal() // initialized with defaults
$("#myModal").modal({ keyboard: false }) // initialized with now keyboard
$("#myModal").modal('show') // initializes and invokes show immediately afterqwe2
---
### OPTIONS
Options should be sparse and add universal value. We should pick the right defaults.
All plugins should have a default object which can be modified to effect all instance's default options. The defaults object should be available via `$.fn.plugin.defaults`.
$.fn.modal.defaults = { … }
An options definition should take the following form:
*noun*: *adjective* - describes or modifies a quality of an instance
examples:
backdrop: true
keyboard: false
placement: 'top'
---
### EVENTS
All events should have an infinitive and past participle form. The infinitive is fired just before an action takes place, the past participle on completion of the action.
show | shown
hide | hidden
---
### CONSTRUCTORS
Each plugin should expose it's raw constructor on a `Constructor` property -- accessed in the following way:
$.fn.popover.Constructor
---
### DATA ACCESSOR
Each plugin stores a copy of the invoked class on an object. This class instance can be accessed directly through jQuery's data API like this:
$('[rel=popover]').data('popover') instanceof $.fn.popover.Constructor
---
### DATA ATTRIBUTES
Data attributes should take the following form:
- data-{{verb}}={{plugin}} - defines main interaction
- data-target || href^=# - defined on "control" element (if element controls an element other than self)
- data-{{noun}} - defines class instance options
examples:
// control other targets
data-toggle="modal" data-target="#foo"
data-toggle="collapse" data-target="#foo" data-parent="#bar"
// defined on element they control
data-spy="scroll"
data-dismiss="modal"
data-dismiss="alert"
data-toggle="dropdown"
data-toggle="button"
data-toggle="buttons-checkbox"
data-toggle="buttons-radio"

View File

@ -0,0 +1,94 @@
/* ==========================================================
* bootstrap-alert.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#alerts
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ){
"use strict"
/* ALERT CLASS DEFINITION
* ====================== */
var dismiss = '[data-dismiss="alert"]'
, Alert = function ( el ) {
$(el).on('click', dismiss, this.close)
}
Alert.prototype = {
constructor: Alert
, close: function ( e ) {
var $this = $(this)
, selector = $this.attr('data-target')
, $parent
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
$parent = $(selector)
$parent.trigger('close')
e && e.preventDefault()
$parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
$parent
.trigger('close')
.removeClass('in')
function removeElement() {
$parent
.trigger('closed')
.remove()
}
$.support.transition && $parent.hasClass('fade') ?
$parent.on($.support.transition.end, removeElement) :
removeElement()
}
}
/* ALERT PLUGIN DEFINITION
* ======================= */
$.fn.alert = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('alert')
if (!data) $this.data('alert', (data = new Alert(this)))
if (typeof option == 'string') data[option].call($this)
})
}
$.fn.alert.Constructor = Alert
/* ALERT DATA-API
* ============== */
$(function () {
$('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
})
}( window.jQuery );

View File

@ -0,0 +1,100 @@
/* ============================================================
* bootstrap-button.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#buttons
* ============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
/* BUTTON PUBLIC CLASS DEFINITION
* ============================== */
var Button = function ( element, options ) {
this.$element = $(element)
this.options = $.extend({}, $.fn.button.defaults, options)
}
Button.prototype = {
constructor: Button
, setState: function ( state ) {
var d = 'disabled'
, $el = this.$element
, data = $el.data()
, val = $el.is('input') ? 'val' : 'html'
state = state + 'Text'
data.resetText || $el.data('resetText', $el[val]())
$el[val](data[state] || this.options[state])
// push to event loop to allow forms to submit
setTimeout(function () {
state == 'loadingText' ?
$el.addClass(d).attr(d, d) :
$el.removeClass(d).removeAttr(d)
}, 0)
}
, toggle: function () {
var $parent = this.$element.parent('[data-toggle="buttons-radio"]')
$parent && $parent
.find('.active')
.removeClass('active')
this.$element.toggleClass('active')
}
}
/* BUTTON PLUGIN DEFINITION
* ======================== */
$.fn.button = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('button')
, options = typeof option == 'object' && option
if (!data) $this.data('button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
})
}
$.fn.button.defaults = {
loadingText: 'loading...'
}
$.fn.button.Constructor = Button
/* BUTTON DATA-API
* =============== */
$(function () {
$('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
$btn.button('toggle')
})
})
}( window.jQuery );

View File

@ -0,0 +1,161 @@
/* ==========================================================
* bootstrap-carousel.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#carousel
* ==========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ){
"use strict"
/* CAROUSEL CLASS DEFINITION
* ========================= */
var Carousel = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.carousel.defaults, options)
this.options.slide && this.slide(this.options.slide)
this.options.pause == 'hover' && this.$element
.on('mouseenter', $.proxy(this.pause, this))
.on('mouseleave', $.proxy(this.cycle, this))
}
Carousel.prototype = {
cycle: function () {
this.interval = setInterval($.proxy(this.next, this), this.options.interval)
return this
}
, to: function (pos) {
var $active = this.$element.find('.active')
, children = $active.parent().children()
, activePos = children.index($active)
, that = this
if (pos > (children.length - 1) || pos < 0) return
if (this.sliding) {
return this.$element.one('slid', function () {
that.to(pos)
})
}
if (activePos == pos) {
return this.pause().cycle()
}
return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
}
, pause: function () {
clearInterval(this.interval)
this.interval = null
return this
}
, next: function () {
if (this.sliding) return
return this.slide('next')
}
, prev: function () {
if (this.sliding) return
return this.slide('prev')
}
, slide: function (type, next) {
var $active = this.$element.find('.active')
, $next = next || $active[type]()
, isCycling = this.interval
, direction = type == 'next' ? 'left' : 'right'
, fallback = type == 'next' ? 'first' : 'last'
, that = this
this.sliding = true
isCycling && this.pause()
$next = $next.length ? $next : this.$element.find('.item')[fallback]()
if ($next.hasClass('active')) return
if (!$.support.transition && this.$element.hasClass('slide')) {
this.$element.trigger('slide')
$active.removeClass('active')
$next.addClass('active')
this.sliding = false
this.$element.trigger('slid')
} else {
$next.addClass(type)
$next[0].offsetWidth // force reflow
$active.addClass(direction)
$next.addClass(direction)
this.$element.trigger('slide')
this.$element.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') }, 0)
})
}
isCycling && this.cycle()
return this
}
}
/* CAROUSEL PLUGIN DEFINITION
* ========================== */
$.fn.carousel = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('carousel')
, options = typeof option == 'object' && option
if (!data) $this.data('carousel', (data = new Carousel(this, options)))
if (typeof option == 'number') data.to(option)
else if (typeof option == 'string' || (option = options.slide)) data[option]()
else data.cycle()
})
}
$.fn.carousel.defaults = {
interval: 5000
, pause: 'hover'
}
$.fn.carousel.Constructor = Carousel
/* CAROUSEL DATA-API
* ================= */
$(function () {
$('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
var $this = $(this), href
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
, options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
$target.carousel(options)
e.preventDefault()
})
})
}( window.jQuery );

View File

@ -0,0 +1,138 @@
/* =============================================================
* bootstrap-collapse.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#collapse
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
var Collapse = function ( element, options ) {
this.$element = $(element)
this.options = $.extend({}, $.fn.collapse.defaults, options)
if (this.options["parent"]) {
this.$parent = $(this.options["parent"])
}
this.options.toggle && this.toggle()
}
Collapse.prototype = {
constructor: Collapse
, dimension: function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
, show: function () {
var dimension = this.dimension()
, scroll = $.camelCase(['scroll', dimension].join('-'))
, actives = this.$parent && this.$parent.find('.in')
, hasData
if (actives && actives.length) {
hasData = actives.data('collapse')
actives.collapse('hide')
hasData || actives.data('collapse', null)
}
this.$element[dimension](0)
this.transition('addClass', 'show', 'shown')
this.$element[dimension](this.$element[0][scroll])
}
, hide: function () {
var dimension = this.dimension()
this.reset(this.$element[dimension]())
this.transition('removeClass', 'hide', 'hidden')
this.$element[dimension](0)
}
, reset: function ( size ) {
var dimension = this.dimension()
this.$element
.removeClass('collapse')
[dimension](size || 'auto')
[0].offsetWidth
this.$element[size ? 'addClass' : 'removeClass']('collapse')
return this
}
, transition: function ( method, startEvent, completeEvent ) {
var that = this
, complete = function () {
if (startEvent == 'show') that.reset()
that.$element.trigger(completeEvent)
}
this.$element
.trigger(startEvent)
[method]('in')
$.support.transition && this.$element.hasClass('collapse') ?
this.$element.one($.support.transition.end, complete) :
complete()
}
, toggle: function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
}
/* COLLAPSIBLE PLUGIN DEFINITION
* ============================== */
$.fn.collapse = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('collapse')
, options = typeof option == 'object' && option
if (!data) $this.data('collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.collapse.defaults = {
toggle: true
}
$.fn.collapse.Constructor = Collapse
/* COLLAPSIBLE DATA-API
* ==================== */
$(function () {
$('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) {
var $this = $(this), href
, target = $this.attr('data-target')
|| e.preventDefault()
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
, option = $(target).data('collapse') ? 'toggle' : $this.data()
$(target).collapse(option)
})
})
}( window.jQuery );

View File

@ -0,0 +1,92 @@
/* ============================================================
* bootstrap-dropdown.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#dropdowns
* ============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
/* DROPDOWN CLASS DEFINITION
* ========================= */
var toggle = '[data-toggle="dropdown"]'
, Dropdown = function ( element ) {
var $el = $(element).on('click.dropdown.data-api', this.toggle)
$('html').on('click.dropdown.data-api', function () {
$el.parent().removeClass('open')
})
}
Dropdown.prototype = {
constructor: Dropdown
, toggle: function ( e ) {
var $this = $(this)
, selector = $this.attr('data-target')
, $parent
, isActive
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
$parent = $(selector)
$parent.length || ($parent = $this.parent())
isActive = $parent.hasClass('open')
clearMenus()
!isActive && $parent.toggleClass('open')
return false
}
}
function clearMenus() {
$(toggle).parent().removeClass('open')
}
/* DROPDOWN PLUGIN DEFINITION
* ========================== */
$.fn.dropdown = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('dropdown')
if (!data) $this.data('dropdown', (data = new Dropdown(this)))
if (typeof option == 'string') data[option].call($this)
})
}
$.fn.dropdown.Constructor = Dropdown
/* APPLY TO STANDARD DROPDOWN ELEMENTS
* =================================== */
$(function () {
$('html').on('click.dropdown.data-api', clearMenus)
$('body').on('click.dropdown.data-api', toggle, Dropdown.prototype.toggle)
})
}( window.jQuery );

View File

@ -0,0 +1,210 @@
/* =========================================================
* bootstrap-modal.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#modals
* =========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
!function( $ ){
"use strict"
/* MODAL CLASS DEFINITION
* ====================== */
var Modal = function ( content, options ) {
this.options = options
this.$element = $(content)
.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
}
Modal.prototype = {
constructor: Modal
, toggle: function () {
return this[!this.isShown ? 'show' : 'hide']()
}
, show: function () {
var that = this
if (this.isShown) return
$('body').addClass('modal-open')
this.isShown = true
this.$element.trigger('show')
escape.call(this)
backdrop.call(this, function () {
var transition = $.support.transition && that.$element.hasClass('fade')
!that.$element.parent().length && that.$element.appendTo(document.body) //don't move modals dom position
that.$element
.show()
if (transition) {
that.$element[0].offsetWidth // force reflow
}
that.$element.addClass('in')
transition ?
that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
that.$element.trigger('shown')
})
}
, hide: function ( e ) {
e && e.preventDefault()
if (!this.isShown) return
var that = this
this.isShown = false
$('body').removeClass('modal-open')
escape.call(this)
this.$element
.trigger('hide')
.removeClass('in')
$.support.transition && this.$element.hasClass('fade') ?
hideWithTransition.call(this) :
hideModal.call(this)
}
}
/* MODAL PRIVATE METHODS
* ===================== */
function hideWithTransition() {
var that = this
, timeout = setTimeout(function () {
that.$element.off($.support.transition.end)
hideModal.call(that)
}, 500)
this.$element.one($.support.transition.end, function () {
clearTimeout(timeout)
hideModal.call(that)
})
}
function hideModal( that ) {
this.$element
.hide()
.trigger('hidden')
backdrop.call(this)
}
function backdrop( callback ) {
var that = this
, animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) {
var doAnimate = $.support.transition && animate
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
.appendTo(document.body)
if (this.options.backdrop != 'static') {
this.$backdrop.click($.proxy(this.hide, this))
}
if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
this.$backdrop.addClass('in')
doAnimate ?
this.$backdrop.one($.support.transition.end, callback) :
callback()
} else if (!this.isShown && this.$backdrop) {
this.$backdrop.removeClass('in')
$.support.transition && this.$element.hasClass('fade')?
this.$backdrop.one($.support.transition.end, $.proxy(removeBackdrop, this)) :
removeBackdrop.call(this)
} else if (callback) {
callback()
}
}
function removeBackdrop() {
this.$backdrop.remove()
this.$backdrop = null
}
function escape() {
var that = this
if (this.isShown && this.options.keyboard) {
$(document).on('keyup.dismiss.modal', function ( e ) {
e.which == 27 && that.hide()
})
} else if (!this.isShown) {
$(document).off('keyup.dismiss.modal')
}
}
/* MODAL PLUGIN DEFINITION
* ======================= */
$.fn.modal = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('modal')
, options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option]()
else if (options.show) data.show()
})
}
$.fn.modal.defaults = {
backdrop: true
, keyboard: true
, show: true
}
$.fn.modal.Constructor = Modal
/* MODAL DATA-API
* ============== */
$(function () {
$('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
var $this = $(this), href
, $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
, option = $target.data('modal') ? 'toggle' : $.extend({}, $target.data(), $this.data())
e.preventDefault()
$target.modal(option)
})
})
}( window.jQuery );

View File

@ -0,0 +1,95 @@
/* ===========================================================
* bootstrap-popover.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#popovers
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================================================== */
!function( $ ) {
"use strict"
var Popover = function ( element, options ) {
this.init('popover', element, options)
}
/* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
========================================== */
Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
constructor: Popover
, setContent: function () {
var $tip = this.tip()
, title = this.getTitle()
, content = this.getContent()
$tip.find('.popover-title')[ $.type(title) == 'object' ? 'append' : 'html' ](title)
$tip.find('.popover-content > *')[ $.type(content) == 'object' ? 'append' : 'html' ](content)
$tip.removeClass('fade top bottom left right in')
}
, hasContent: function () {
return this.getTitle() || this.getContent()
}
, getContent: function () {
var content
, $e = this.$element
, o = this.options
content = $e.attr('data-content')
|| (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
content = content.toString().replace(/(^\s*|\s*$)/, "")
return content
}
, tip: function() {
if (!this.$tip) {
this.$tip = $(this.options.template)
}
return this.$tip
}
})
/* POPOVER PLUGIN DEFINITION
* ======================= */
$.fn.popover = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('popover')
, options = typeof option == 'object' && option
if (!data) $this.data('popover', (data = new Popover(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.popover.Constructor = Popover
$.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
placement: 'right'
, content: ''
, template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
})
}( window.jQuery );

View File

@ -0,0 +1,125 @@
/* =============================================================
* bootstrap-scrollspy.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#scrollspy
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================== */
!function ( $ ) {
"use strict"
/* SCROLLSPY CLASS DEFINITION
* ========================== */
function ScrollSpy( element, options) {
var process = $.proxy(this.process, this)
, $element = $(element).is('body') ? $(window) : $(element)
, href
this.options = $.extend({}, $.fn.scrollspy.defaults, options)
this.$scrollElement = $element.on('scroll.scroll.data-api', process)
this.selector = (this.options.target
|| ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
|| '') + ' .nav li > a'
this.$body = $('body').on('click.scroll.data-api', this.selector, process)
this.refresh()
this.process()
}
ScrollSpy.prototype = {
constructor: ScrollSpy
, refresh: function () {
this.targets = this.$body
.find(this.selector)
.map(function () {
var href = $(this).attr('href')
return /^#\w/.test(href) && $(href).length ? href : null
})
this.offsets = $.map(this.targets, function (id) {
return $(id).position().top
})
}
, process: function () {
var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
, offsets = this.offsets
, targets = this.targets
, activeTarget = this.activeTarget
, i
for (i = offsets.length; i--;) {
activeTarget != targets[i]
&& scrollTop >= offsets[i]
&& (!offsets[i + 1] || scrollTop <= offsets[i + 1])
&& this.activate( targets[i] )
}
}
, activate: function (target) {
var active
this.activeTarget = target
this.$body
.find(this.selector).parent('.active')
.removeClass('active')
active = this.$body
.find(this.selector + '[href="' + target + '"]')
.parent('li')
.addClass('active')
if ( active.parent('.dropdown-menu') ) {
active.closest('li.dropdown').addClass('active')
}
}
}
/* SCROLLSPY PLUGIN DEFINITION
* =========================== */
$.fn.scrollspy = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('scrollspy')
, options = typeof option == 'object' && option
if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.scrollspy.Constructor = ScrollSpy
$.fn.scrollspy.defaults = {
offset: 10
}
/* SCROLLSPY DATA-API
* ================== */
$(function () {
$('[data-spy="scroll"]').each(function () {
var $spy = $(this)
$spy.scrollspy($spy.data())
})
})
}( window.jQuery );

View File

@ -0,0 +1,130 @@
/* ========================================================
* bootstrap-tab.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#tabs
* ========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ======================================================== */
!function( $ ){
"use strict"
/* TAB CLASS DEFINITION
* ==================== */
var Tab = function ( element ) {
this.element = $(element)
}
Tab.prototype = {
constructor: Tab
, show: function () {
var $this = this.element
, $ul = $this.closest('ul:not(.dropdown-menu)')
, selector = $this.attr('data-target')
, previous
, $target
if (!selector) {
selector = $this.attr('href')
selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
}
if ( $this.parent('li').hasClass('active') ) return
previous = $ul.find('.active a').last()[0]
$this.trigger({
type: 'show'
, relatedTarget: previous
})
$target = $(selector)
this.activate($this.parent('li'), $ul)
this.activate($target, $target.parent(), function () {
$this.trigger({
type: 'shown'
, relatedTarget: previous
})
})
}
, activate: function ( element, container, callback) {
var $active = container.find('> .active')
, transition = callback
&& $.support.transition
&& $active.hasClass('fade')
function next() {
$active
.removeClass('active')
.find('> .dropdown-menu > .active')
.removeClass('active')
element.addClass('active')
if (transition) {
element[0].offsetWidth // reflow for transition
element.addClass('in')
} else {
element.removeClass('fade')
}
if ( element.parent('.dropdown-menu') ) {
element.closest('li.dropdown').addClass('active')
}
callback && callback()
}
transition ?
$active.one($.support.transition.end, next) :
next()
$active.removeClass('in')
}
}
/* TAB PLUGIN DEFINITION
* ===================== */
$.fn.tab = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tab')
if (!data) $this.data('tab', (data = new Tab(this)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tab.Constructor = Tab
/* TAB DATA-API
* ============ */
$(function () {
$('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
e.preventDefault()
$(this).tab('show')
})
})
}( window.jQuery );

View File

@ -0,0 +1,270 @@
/* ===========================================================
* bootstrap-tooltip.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#tooltips
* Inspired by the original jQuery.tipsy by Jason Frame
* ===========================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ) {
"use strict"
/* TOOLTIP PUBLIC CLASS DEFINITION
* =============================== */
var Tooltip = function ( element, options ) {
this.init('tooltip', element, options)
}
Tooltip.prototype = {
constructor: Tooltip
, init: function ( type, element, options ) {
var eventIn
, eventOut
this.type = type
this.$element = $(element)
this.options = this.getOptions(options)
this.enabled = true
if (this.options.trigger != 'manual') {
eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
this.$element.on(eventIn, this.options.selector, $.proxy(this.enter, this))
this.$element.on(eventOut, this.options.selector, $.proxy(this.leave, this))
}
this.options.selector ?
(this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
, getOptions: function ( options ) {
options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
if (options.delay && typeof options.delay == 'number') {
options.delay = {
show: options.delay
, hide: options.delay
}
}
return options
}
, enter: function ( e ) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (!self.options.delay || !self.options.delay.show) {
self.show()
} else {
self.hoverState = 'in'
setTimeout(function() {
if (self.hoverState == 'in') {
self.show()
}
}, self.options.delay.show)
}
}
, leave: function ( e ) {
var self = $(e.currentTarget)[this.type](this._options).data(this.type)
if (!self.options.delay || !self.options.delay.hide) {
self.hide()
} else {
self.hoverState = 'out'
setTimeout(function() {
if (self.hoverState == 'out') {
self.hide()
}
}, self.options.delay.hide)
}
}
, show: function () {
var $tip
, inside
, pos
, actualWidth
, actualHeight
, placement
, tp
if (this.hasContent() && this.enabled) {
$tip = this.tip()
this.setContent()
if (this.options.animation) {
$tip.addClass('fade')
}
placement = typeof this.options.placement == 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement
inside = /in/.test(placement)
$tip
.remove()
.css({ top: 0, left: 0, display: 'block' })
.appendTo(inside ? this.$element : document.body)
pos = this.getPosition(inside)
actualWidth = $tip[0].offsetWidth
actualHeight = $tip[0].offsetHeight
switch (inside ? placement.split(' ')[1] : placement) {
case 'bottom':
tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'top':
tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
break
case 'left':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
break
case 'right':
tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
break
}
$tip
.css(tp)
.addClass(placement)
.addClass('in')
}
}
, setContent: function () {
var $tip = this.tip()
$tip.find('.tooltip-inner').html(this.getTitle())
$tip.removeClass('fade in top bottom left right')
}
, hide: function () {
var that = this
, $tip = this.tip()
$tip.removeClass('in')
function removeWithAnimation() {
var timeout = setTimeout(function () {
$tip.off($.support.transition.end).remove()
}, 500)
$tip.one($.support.transition.end, function () {
clearTimeout(timeout)
$tip.remove()
})
}
$.support.transition && this.$tip.hasClass('fade') ?
removeWithAnimation() :
$tip.remove()
}
, fixTitle: function () {
var $e = this.$element
if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
$e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
}
}
, hasContent: function () {
return this.getTitle()
}
, getPosition: function (inside) {
return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
width: this.$element[0].offsetWidth
, height: this.$element[0].offsetHeight
})
}
, getTitle: function () {
var title
, $e = this.$element
, o = this.options
title = $e.attr('data-original-title')
|| (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
title = (title || '').toString().replace(/(^\s*|\s*$)/, "")
return title
}
, tip: function () {
return this.$tip = this.$tip || $(this.options.template)
}
, validate: function () {
if (!this.$element[0].parentNode) {
this.hide()
this.$element = null
this.options = null
}
}
, enable: function () {
this.enabled = true
}
, disable: function () {
this.enabled = false
}
, toggleEnabled: function () {
this.enabled = !this.enabled
}
, toggle: function () {
this[this.tip().hasClass('in') ? 'hide' : 'show']()
}
}
/* TOOLTIP PLUGIN DEFINITION
* ========================= */
$.fn.tooltip = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('tooltip')
, options = typeof option == 'object' && option
if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.tooltip.Constructor = Tooltip
$.fn.tooltip.defaults = {
animation: true
, delay: 0
, selector: false
, placement: 'top'
, trigger: 'hover'
, title: ''
, template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
}
}( window.jQuery );

View File

@ -0,0 +1,51 @@
/* ===================================================
* bootstrap-transition.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#transitions
* ===================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================== */
!function( $ ) {
$(function () {
"use strict"
/* CSS TRANSITION SUPPORT (https://gist.github.com/373874)
* ======================================================= */
$.support.transition = (function () {
var thisBody = document.body || document.documentElement
, thisStyle = thisBody.style
, support = thisStyle.transition !== undefined || thisStyle.WebkitTransition !== undefined || thisStyle.MozTransition !== undefined || thisStyle.MsTransition !== undefined || thisStyle.OTransition !== undefined
return support && {
end: (function () {
var transitionEnd = "TransitionEnd"
if ( $.browser.webkit ) {
transitionEnd = "webkitTransitionEnd"
} else if ( $.browser.mozilla ) {
transitionEnd = "transitionend"
} else if ( $.browser.opera ) {
transitionEnd = "oTransitionEnd"
}
return transitionEnd
}())
}
})()
})
}( window.jQuery );

View File

@ -0,0 +1,271 @@
/* =============================================================
* bootstrap-typeahead.js v2.0.2
* http://twitter.github.com/bootstrap/javascript.html#typeahead
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function( $ ){
"use strict"
var Typeahead = function ( element, options ) {
this.$element = $(element)
this.options = $.extend({}, $.fn.typeahead.defaults, options)
this.matcher = this.options.matcher || this.matcher
this.sorter = this.options.sorter || this.sorter
this.highlighter = this.options.highlighter || this.highlighter
this.$menu = $(this.options.menu).appendTo('body')
this.source = this.options.source
this.shown = false
this.listen()
}
Typeahead.prototype = {
constructor: Typeahead
, select: function () {
var val = this.$menu.find('.active').attr('data-value')
this.$element.val(val)
this.$element.change();
return this.hide()
}
, show: function () {
var pos = $.extend({}, this.$element.offset(), {
height: this.$element[0].offsetHeight
})
this.$menu.css({
top: pos.top + pos.height
, left: pos.left
})
this.$menu.show()
this.shown = true
return this
}
, hide: function () {
this.$menu.hide()
this.shown = false
return this
}
, lookup: function (event) {
var that = this
, items
, q
this.query = this.$element.val()
if (!this.query) {
return this.shown ? this.hide() : this
}
items = $.grep(this.source, function (item) {
if (that.matcher(item)) return item
})
items = this.sorter(items)
if (!items.length) {
return this.shown ? this.hide() : this
}
return this.render(items.slice(0, this.options.items)).show()
}
, matcher: function (item) {
return ~item.toLowerCase().indexOf(this.query.toLowerCase())
}
, sorter: function (items) {
var beginswith = []
, caseSensitive = []
, caseInsensitive = []
, item
while (item = items.shift()) {
if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
else if (~item.indexOf(this.query)) caseSensitive.push(item)
else caseInsensitive.push(item)
}
return beginswith.concat(caseSensitive, caseInsensitive)
}
, highlighter: function (item) {
return item.replace(new RegExp('(' + this.query + ')', 'ig'), function ($1, match) {
return '<strong>' + match + '</strong>'
})
}
, render: function (items) {
var that = this
items = $(items).map(function (i, item) {
i = $(that.options.item).attr('data-value', item)
i.find('a').html(that.highlighter(item))
return i[0]
})
items.first().addClass('active')
this.$menu.html(items)
return this
}
, next: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, next = active.next()
if (!next.length) {
next = $(this.$menu.find('li')[0])
}
next.addClass('active')
}
, prev: function (event) {
var active = this.$menu.find('.active').removeClass('active')
, prev = active.prev()
if (!prev.length) {
prev = this.$menu.find('li').last()
}
prev.addClass('active')
}
, listen: function () {
this.$element
.on('blur', $.proxy(this.blur, this))
.on('keypress', $.proxy(this.keypress, this))
.on('keyup', $.proxy(this.keyup, this))
if ($.browser.webkit || $.browser.msie) {
this.$element.on('keydown', $.proxy(this.keypress, this))
}
this.$menu
.on('click', $.proxy(this.click, this))
.on('mouseenter', 'li', $.proxy(this.mouseenter, this))
}
, keyup: function (e) {
switch(e.keyCode) {
case 40: // down arrow
case 38: // up arrow
break
case 9: // tab
case 13: // enter
if (!this.shown) return
this.select()
break
case 27: // escape
if (!this.shown) return
this.hide()
break
default:
this.lookup()
}
e.stopPropagation()
e.preventDefault()
}
, keypress: function (e) {
if (!this.shown) return
switch(e.keyCode) {
case 9: // tab
case 13: // enter
case 27: // escape
e.preventDefault()
break
case 38: // up arrow
e.preventDefault()
this.prev()
break
case 40: // down arrow
e.preventDefault()
this.next()
break
}
e.stopPropagation()
}
, blur: function (e) {
var that = this
setTimeout(function () { that.hide() }, 150)
}
, click: function (e) {
e.stopPropagation()
e.preventDefault()
this.select()
}
, mouseenter: function (e) {
this.$menu.find('.active').removeClass('active')
$(e.currentTarget).addClass('active')
}
}
/* TYPEAHEAD PLUGIN DEFINITION
* =========================== */
$.fn.typeahead = function ( option ) {
return this.each(function () {
var $this = $(this)
, data = $this.data('typeahead')
, options = typeof option == 'object' && option
if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.typeahead.defaults = {
source: []
, items: 8
, menu: '<ul class="typeahead dropdown-menu"></ul>'
, item: '<li><a href="#"></a></li>'
}
$.fn.typeahead.Constructor = Typeahead
/* TYPEAHEAD DATA-API
* ================== */
$(function () {
$('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
var $this = $(this)
if ($this.data('typeahead')) return
e.preventDefault()
$this.typeahead($this.data())
})
})
}( window.jQuery );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,196 @@
# OpenID Connect Client
## Overview
This is the Client, a Spring Security AuthenticationFilter, to OpenID Connect Java Spring Server described by [OpenID Connect Standard].
## Configure
Configure the OpenIDConnectAuthenticationFilter by adding the XML to your application context security like so:
<security:http auto-config="false"
use-expressions="true"
disable-url-rewriting="true"
entry-point-ref="authenticationEntryPoint"
pattern="/**">
<security:intercept-url
pattern="/somepath/**"
access="denyAll" />
<security:custom-filter
before="PRE_AUTH_FILTER
ref="openIdConnectAuthenticationFilter" />
<security:intercept-url
pattern="/**"
access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" />
<security:logout />
<securityLremember-me user-service-ref="myUserDetailsService"
</security:http>
<bean id="authenticationEntryPoint"
class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl"
value="/openid_connect_login"/>
</bean>
<security:authentication-manager alias="authenticationManager" />
<bean id="openIdConnectAuthenticationProvider"
class='org.mitre.openid.connect.client.OpenIdConnectAuthenticationProvider">
<property name="userDetaulsService" ref="myUserDetailsService"/>
</bean>
<bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="errorRedirectURI"
value="/login.jsp?authfail=openid" />
<property name="authorizationEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/openidconnect/auth" />
<property name="tokenEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/checkid" />
<property name="checkIDEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/checkid" />
<property name="clientId"
value="someClientId" />
<property name="clientSecret" value="someClientSecret" />
</bean>
You will need to implement your own UserDetailsService and configure as the above does with the reference to *myUserDetailsService*.
## Proposed Account Chooser UI Application Extension
The following proposed extension is in response to [Issue #39].
### Account Chooser Protocol
The following describes the protocol between the Client and Account Chooser UI application introduced in [Issue #39].
#### Authorization when using Account Chooser Code Flow
The Authorization when using Account Chooser Code Flow goes through the following steps.
1. Client prepares an Account Chooser Request containing the desired request parameters.
2. Client sends a request to the Account Chooser.
3. Account Chooser presents a selection of OpenID Connect (OIDC) Servers from which the End-User must select from.
4. End-User selects an OIDC.
5. Account Chooser Sends the End-User back to the Client with key value of the OIDC End-User selected.
6. The Client begins the Authorization flow desrcribed in [Authorization Code Flow][OpenID Connect Standard] of the [OpenID Connect Standard].
#### Account Chooser Request
When the End-User wishes to access a Protected Resource, and the End-User Authorization has not yet been obtained, the Client will redirect the End-User to Account Chooser.
Account Chooser MUST support the use of the HTTP "GET" and "POST" methods defined in RFC 2616 [RFC2616].
Clients MAY use the HTTP "GET" or "POST" method to send the Account Chooser Request to the Account Chooser. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
#### Client Prepares an Account Chooser Request
The Client prepares an Account Chooser Request to the Account Chooser with the request parameters using the HTTP "GET" or "POST" method.
The required Account Chooser Request parameters are as follows:
* redirect_uri - A redirection URI where the response will be sent.
There is one method to construct and send the request to the Account Chooser:
a. Simple Request Method
#### Simple Request Method
The Client prepares an Account Chooser Request to the Account Chooser using the appropriate parameters. If using the HTTP "GET" method, the request parameters are serialized using URI query string serialization. If using the HTTP "POST" method, the request parameters are serialized using form serialization.
The following is a non-normative example of an Account Chooser Request URL. Line wraps are for display purposes only.
http://server.example.com/chooser?
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
#### Client sends a request to the Account Chooser
Having constructed the Account Chooser Request, the Client sends it to the Account Chooser. This MAY happen via redirect, hyperlinking, or any other means of directing the User-Agent to the Account Chooser URL.
Following is a non-normative example using HTTP redirect. Line wraps are for display purposes only.
HTTP/1.1 302 Found
Location: https://server.example.com/chooser?
redirect_uri=https%3A%2F%2Fclient.example.com%2Fopenid_connect_login
#### Account Chooser Sends the End-User back to the Client
After the End-User has select an OpenID Connect Server, it issues an Account Chooser Response and delivers it to the Client by adding the response parameters to redirect_uri specified in the Account Choose Request using the "application/x-www-form-urlencoded" format.
The following response parameters are included:
* oidc_alias - REQUIRED. The key used to configure the Client for its request of the selected OIDC server.
The following is non-normative example of a responses. Line wraps are for display purposes only.
HTTP/1.1 302 Found
Location: https://client.example.com/openid_connect_login?
oidc_alias=OIDC%20Server%201
#### End-User refuses to select an OIDC Server
If the End-User refuses to select an OIDC server, the Account Chooser MUST return an error response. The Account Chooser returns the Client via the redirection URI specified in the Account Chooser Request with the appropriate error parameters. No other parameters SHOULD be returned.
The error response parameters are the following:
* error - REQUIRED. The error code.
* error_description - OPTIONAL. A human-readable UTF-8 encoded text description of the error.
The response parameters are added to the query component of the redirection URI.
The following is a non-normative example. Line wraps after the second line are for the display purposes only.
HTTP/1.1 302 Found
Location: https://client.example.com/openid_connect_login?
error=end_user_cancelled
&error_description=The%20end%20user%20refused%20to%20select%20an%20OIDC%20server
### Modification to existing Client
#### Modifications to Client Configuration
The configuration of the filter would change by adding a OIDCServers property to the Client containing a map of OIDC servers, and a AccountChooserURI to denote the URI of the Account Chooser like so:
<bean id="openIdConnectAuthenticationFilter"
class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter">
<property name="errorRedirectURI" value="/login.jsp?authfail=openid" />
<property name="authenticationManager" ref="authenticationManager" />
<property name="AccountChooserURI"
value="http://sever.example.com:8080/account-chooser" />
<property name="oidcServerConfigs">
<map>
<entry key="OIDC Server 1">
<bean class="org.mitre.openid.connect.client.OIDCServerConfiguration">
<property name="authorizationEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/openidconnect/auth" />
<property name="tokenEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/checkid" />
<property name="checkIDEndpointURI"
value="http://sever.example.com:8080/openid-connect-server/checkid" />
<property name="clientId"
value="someClientId" />
<property name="clientSecret" value="someClientSecret" />
</bean>
</entry>
<entry key="OIDC Server 2">
...
</map>
</property>
</bean>
In cases where the Account Chooser will not be used, the Client will be configured with authorizationEndpointURI, tokenEndpointURI, checkIDEndpointURI, clientId, and clientSecret as the Client is presently.
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html "OpenID Connect Standard 1.0"
[OpenID Connect Standard]: http://openid.net/specs/openid-connect-standard-1_0.html#code_flow "Authorization Code Flow, OpenID Connect Standard"
[Issue #39]: http://github.com/jricher/OpenID-Connect-Java-Spring-Server/issues/39 "Issue #39 -- Multiple Point Client"

View File

@ -0,0 +1,83 @@
/*******************************************************************************
* Copyright 2012 The MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.mitre.openid.connect.client;
/**
* @author nemonik
*
*/
public class OIDCServerConfiguration {
private String authorizationEndpointURI;
private String tokenEndpointURI;
private String checkIDEndpointURI;
private String clientSecret;
private String clientId;
public String getAuthorizationEndpointURI() {
return authorizationEndpointURI;
}
public String getCheckIDEndpointURI() {
return checkIDEndpointURI;
}
public String getClientId() {
return clientId;
}
public String getClientSecret() {
return clientSecret;
}
public String getTokenEndpointURI() {
return tokenEndpointURI;
}
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
this.authorizationEndpointURI = authorizationEndpointURI;
}
public void setCheckIDEndpointURI(String checkIDEndpointURI) {
this.checkIDEndpointURI = checkIDEndpointURI;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public void setTokenEndpointURI(String tokenEndpointURI) {
this.tokenEndpointURI = tokenEndpointURI;
}
@Override
public String toString() {
return "OIDCServerConfiguration [authorizationEndpointURI="
+ authorizationEndpointURI + ", tokenEndpointURI="
+ tokenEndpointURI + ", checkIDEndpointURI="
+ checkIDEndpointURI + ", clientSecret=" + clientSecret
+ ", clientId=" + clientId + "]";
}
}

View File

@ -38,6 +38,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.mitre.openid.connect.model.IdToken;
@ -61,40 +62,7 @@ import com.google.gson.JsonParser;
/**
* The OpenID Connect Authentication Filter
*
* Configured like:
*
* <security:http auto-config="false" use-expressions="true"
* disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
* pattern="/**">
*
* <security:intercept-url pattern="/somepath/**" access="denyAll" />
*
* <security:custom-filter before="PRE_AUTH_FILTER "
* ref="openIdConnectAuthenticationFilter" />
*
* <security:intercept-url pattern="/**"
* access="hasAnyRole('ROLE_USER','ROLE_ADMIN')" /> <security:logout />
* </security:http>
*
* <bean id="authenticationEntryPoint" class=
* "org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint"
* > <property name="loginFormUrl" value="/openid_connect_login"/> </bean>
*
* <security:authentication-manager alias="authenticationManager" /> <bean
* id="openIdConnectAuthenticationFilter"
* class="org.mitre.openid.connect.client.OpenIdConnectAuthenticationFilter">
*
* <property name="authenticationManager" ref="authenticationManager" />
* <property name="errorRedirectURI" value="/login.jsp?authfail=openid" /> <!--
* TODO: or would this be value="/login.jsp?authfail=openid_connect" -->
* <property name="authorizationEndpointURI" value=
* "http://sever.example.com:8080/openid-connect-server/openidconnect/auth" />
* <property name="tokenEndpointURI"
* value="http://sever.example.com:8080/openid-connect-server/checkid" />
* <property name="checkIDEndpointURI"
* value="http://sever.example.com:8080/openid-connect-server/checkid" />
* <property name="clientId" value="someClientId" /> <property
* name="clientSecret" value="someClientSecret" /> </bean>
* See README.md to to configure
*
* @author nemonik
*
@ -107,6 +75,7 @@ public class OpenIdConnectAuthenticationFilter extends
private final static int KEY_SIZE = 1024;
private final static String SIGNING_ALGORITHM = "SHA256withRSA";
private final static String NONCE_SIGNATURE_COOKIE_NAME = "nonce";
private final static String OIDC_ALIAS_COOKIE_NAME = "oidc_alias";
private final static String FILTER_PROCESSES_URL = "/openid_connect_login";
/**
@ -257,15 +226,11 @@ public class OpenIdConnectAuthenticationFilter extends
private String errorRedirectURI;
private String authorizationEndpointURI;
private OIDCServerConfiguration oidcServerConfig;
private String tokenEndpointURI;
private String accountChooserURI;
private String checkIDEndpointURI;
private String clientSecret;
private String clientId;
private Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs = new HashMap<String, OIDCServerConfiguration>();
private String scope;
@ -282,14 +247,10 @@ public class OpenIdConnectAuthenticationFilter extends
*/
protected OpenIdConnectAuthenticationFilter() {
super(FILTER_PROCESSES_URL);
oidcServerConfig = new OIDCServerConfiguration();
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.authentication.
* AbstractAuthenticationProcessingFilter#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
@ -297,20 +258,42 @@ public class OpenIdConnectAuthenticationFilter extends
Assert.notNull(errorRedirectURI,
"An Error Redirect URI must be supplied");
Assert.notNull(authorizationEndpointURI,
"An Authorization Endpoint URI must be supplied");
try {
Assert.notNull(tokenEndpointURI,
"A Token ID Endpoint URI must be supplied");
// Validating configuration w/ Account Chooser UI Application
// settings
Assert.notNull(checkIDEndpointURI,
"A Check ID Endpoint URI must be supplied");
Assert.notNull(
oidcServerConfigs,
"Server Configurations must be supplied if the Account Chooser UI Application is to be used.");
Assert.notNull(clientId, "A Client ID must be supplied");
Assert.notNull(
accountChooserURI,
"Account Chooser URI must be supplied if the Account Chooser UI Application is to be used.");
Assert.notNull(clientSecret, "A Client Secret must be supplied");
} catch (Exception e) {
// Failing over to validating configuration w/o Account Chooser UI
// Application settings
Assert.notNull(oidcServerConfig.getAuthorizationEndpointURI(),
"An Authorization Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getTokenEndpointURI(),
"A Token ID Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getCheckIDEndpointURI(),
"A Check ID Endpoint URI must be supplied");
Assert.notNull(oidcServerConfig.getClientId(),
"A Client ID must be supplied");
Assert.notNull(oidcServerConfig.getClientSecret(),
"A Client Secret must be supplied");
}
KeyPairGenerator keyPairGenerator;
try {
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
@ -341,29 +324,73 @@ public class OpenIdConnectAuthenticationFilter extends
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
if (request.getParameter("error") != null) {
// Enter AuthenticationFilter here...
if (StringUtils.isNotBlank(request.getParameter("error"))) {
handleError(request, response);
} else {
} else if (request.getParameter("code") != null) {
// Determine if the Authorization Endpoint issued an
// authorization grant
return handleAuthorizationGrantResponse(request);
if (request.getParameter("code") != null) {
} else if (StringUtils.isNotBlank(accountChooserURI)) {
return handleAuthorizationGrantResponse(request);
String oidcAlias = request.getParameter("oidc_alias");
if (StringUtils.isNotBlank(oidcAlias)) {
// Account Chooser UI selects the appropriate OIDC Server configuration
OIDCServerConfiguration oidcServerConfig = oidcServerConfigs
.get(oidcAlias);
if (oidcServerConfig != null) {
Cookie oidcAliasCookie = new Cookie(OIDC_ALIAS_COOKIE_NAME, oidcAlias);
response.addCookie(oidcAliasCookie);
handleAuthorizationRequest(request, response, oidcServerConfig);
} else {
throw new AuthenticationServiceException(
"Security Filter not configured for OIDC Alias: "
+ oidcAlias);
}
} else {
handleAuthorizationRequest(request, response);
// redirect the End-User to the configured Account Chooser UI
Map<String, String> urlVariables = new HashMap<String, String>();
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter
.buildRedirectURI(request, null));
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
accountChooserURI, urlVariables));
}
} else {
// Use configuration that doesn't involve the Account Chooser UI.
handleAuthorizationRequest(request, response, this.oidcServerConfig);
}
return null;
}
public String getAccountChooserURI() {
return accountChooserURI;
}
public Map<String, ? extends OIDCServerConfiguration> getOidcServerConfigs() {
return oidcServerConfigs;
}
/**
* Handles the authorization grant response
*
@ -377,7 +404,16 @@ public class OpenIdConnectAuthenticationFilter extends
HttpServletRequest request) {
final boolean debug = logger.isDebugEnabled();
OIDCServerConfiguration serverConfig = this.oidcServerConfig;
// Which OIDC configuration?
Cookie oidcAliasCookie = WebUtils.getCookie(request, OIDC_ALIAS_COOKIE_NAME);
if (oidcAliasCookie != null) {
serverConfig = oidcServerConfigs.get(oidcAliasCookie.getValue());
}
String authorizationGrant = request.getParameter("code");
// Handle Token Endpoint interaction
@ -408,18 +444,18 @@ public class OpenIdConnectAuthenticationFilter extends
.buildRedirectURI(request, new String[] { "code" }));
// pass clientId and clientSecret in post of request
form.add("client_id", clientId);
form.add("client_secret", clientSecret);
form.add("client_id", serverConfig.getClientId());
form.add("client_secret", serverConfig.getClientSecret());
if (debug) {
logger.debug("tokenEndpointURI = " + tokenEndpointURI);
logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointURI());
logger.debug("form = " + form);
}
String jsonString = null;
try {
jsonString = restTemplate.postForObject(tokenEndpointURI, form,
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointURI(), form,
String.class);
} catch (HttpClientErrorException httpClientErrorException) {
@ -473,11 +509,10 @@ public class OpenIdConnectAuthenticationFilter extends
String h64 = parts.get(0);
String c64 = parts.get(1);
String s64 = parts.get(2);
logger.debug("h64 = " + h64);
logger.debug("c64 = " + c64);
logger.debug("s64 = " + s64);
} catch (Exception e) {
@ -519,7 +554,7 @@ public class OpenIdConnectAuthenticationFilter extends
jsonString = null;
try {
jsonString = restTemplate.postForObject(checkIDEndpointURI,
jsonString = restTemplate.postForObject(serverConfig.getCheckIDEndpointURI(),
form, String.class);
} catch (HttpClientErrorException httpClientErrorException) {
@ -612,18 +647,20 @@ public class OpenIdConnectAuthenticationFilter extends
* @param response
* The response, needed to set a cookie and do a redirect as part
* of a multi-stage authentication process
* @param serverConfiguration
* @throws IOException
* If an input or output exception occurs
*/
private void handleAuthorizationRequest(HttpServletRequest request,
HttpServletResponse response) throws IOException {
HttpServletResponse response,
OIDCServerConfiguration serverConfiguration) throws IOException {
Map<String, String> urlVariables = new HashMap<String, String>();
// Required parameters:
urlVariables.put("response_type", "code");
urlVariables.put("client_id", clientId);
urlVariables.put("client_id", serverConfiguration.getClientId());
urlVariables.put("scope", scope);
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter
.buildRedirectURI(request, null));
@ -648,7 +685,7 @@ public class OpenIdConnectAuthenticationFilter extends
// TODO: display, prompt, request, request_uri
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
authorizationEndpointURI, urlVariables));
serverConfiguration.getAuthorizationEndpointURI(), urlVariables));
}
/**
@ -685,31 +722,40 @@ public class OpenIdConnectAuthenticationFilter extends
errorRedirectURI, requestParams));
}
public void setAccountChooserURI(String accountChooserURI) {
this.accountChooserURI = accountChooserURI;
}
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
this.authorizationEndpointURI = authorizationEndpointURI;
oidcServerConfig.setAuthorizationEndpointURI(authorizationEndpointURI);
}
public void setCheckIDEndpointURI(String checkIDEndpointURI) {
this.checkIDEndpointURI = checkIDEndpointURI;
oidcServerConfig.setCheckIDEndpointURI(checkIDEndpointURI);
}
public void setClientId(String clientId) {
this.clientId = clientId;
oidcServerConfig.setClientId(clientId);
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
oidcServerConfig.setClientSecret(clientSecret);
}
public void setErrorRedirectURI(String errorRedirectURI) {
this.errorRedirectURI = errorRedirectURI;
}
public void setOidcServerConfigs(
Map<String, ? extends OIDCServerConfiguration> oidcServerConfigs) {
this.oidcServerConfigs = oidcServerConfigs;
}
public void setScope(String scope) {
this.scope = scope;
}
public void setTokenEndpointURI(String tokenEndpointURI) {
this.tokenEndpointURI = tokenEndpointURI;
oidcServerConfig.setTokenEndpointURI(tokenEndpointURI);
}
}

View File

@ -26,7 +26,7 @@ import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
/**
* @author mjwalsh
* @author nemonik
*
*/
public class OpenIdConnectAuthenticationProvider implements

View File

@ -18,6 +18,7 @@
<module>spring-security-oauth/spring-security-oauth2</module>
<module>openid-connect-common</module>
<module>openid-connect-client</module>
<module>account-chooser</module>
<module>openid-connect-server</module>
</modules>
</profile>