From 55d592292bd60b54087e5b0fc312bc772a077ded Mon Sep 17 00:00:00 2001 From: mkk Date: Sun, 5 Jul 2020 23:37:41 +0800 Subject: [PATCH 01/78] Update version 2.0.0, readme --- README.md | 33 ++++++++++++++++++++++----------- authz/pom.xml | 5 +++-- core/pom.xml | 6 +++--- pom.xml | 9 +++------ resources/pom.xml | 5 +++-- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index b736630..0c84be4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@

主要技术及版本

- Spring -- 3.2.2.RELEASE + Spring Boot-- 2.3.1.RELEASE
oltu -- 1.0.2
@@ -35,8 +35,8 @@

开发环境

    -
  • JDK -- 1.7.0_40

  • -
  • Maven -- 3.1.0

  • +
  • JDK -- 1.8.0_40

  • +
  • Maven -- 3.6.0

  • MySQL -- 5.6.23-log

@@ -73,7 +73,7 @@

如何使用

  1. - 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.1.0), 还有MySql(开发用的mysql版本号为5.6) + 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.6.0), 还有MySql(开发用的mysql版本号为5.6)
  2. 下载(或clone)项目到本地 @@ -133,13 +133,26 @@
    • - Version: 0.3 [pending] + Version: 2.0.0 [pending]
      - Date: 2016-07-16 / ------ + Date: 2020-07-05 / ---

        -
      1. (152) - oltu版本升级到1.0.2 并完成测试.

      2. +
      3. 升级使用Spring Boot

      4. (153) - 尝试添加并实现OIDC在 oauth2-shiro中

      5. +
      6. 升级shiro, oltu版本

      7. +
      +
      +
    • +
    • +

      + Version: 0.3 [finished] +
      + Date: 2016-07-16 / 2018-10-17 +

      +
        +
      1. (152) - oltu版本升级到1.0.2 并完成测试.

      2. +
      3. (153) - 尝试添加并实现OIDC在 oauth2-shiro中[canceled]

      4. (161) - 增加必要的代码注释与配置注释, 更易理解

      5. implicit模式不需要带上client_secret

      @@ -179,6 +192,7 @@
    • 2016-07-02 添加在线测试环境

    • 2016-08-17 发布 0.2 版本

    • 2017-01-21 加入到GitHub中, Git@OSC地址: http://git.oschina.net/mkk/oauth2-shiro

    • +
    • 2020-07-05 开始2.0.0版本开发

@@ -239,8 +253,5 @@

若需更多的商业技术支持请联系 sz@qc8.com - 或访问 http://monkeyk.com/kso/

-

- WeChat -

+ diff --git a/authz/pom.xml b/authz/pom.xml index 2403de2..79aba5d 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -6,7 +6,7 @@ com.monkeyk authz - 0.3 + 2.0.0 ${project.artifactId} war @@ -17,6 +17,7 @@ 3.2.2.RELEASE 1.6.12 + ${project.version} 5.1.35 1.0.2 @@ -160,7 +161,7 @@ com.monkeyk core - 0.3 + ${core.version} diff --git a/core/pom.xml b/core/pom.xml index e743206..f69e65a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -6,7 +6,7 @@ com.monkeyk core - 0.3 + 2.0.0 ${project.artifactId} @@ -30,8 +30,8 @@ maven-compiler-plugin 3.2 - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 diff --git a/pom.xml b/pom.xml index a7c1c15..6780aff 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.monkeyk oauth2-shiro - 0.3 + 2.0.0 ${project.artifactId} pom @@ -39,8 +39,8 @@ maven-compiler-plugin 3.2 - 1.7 - 1.7 + 1.8 + 1.8 UTF-8 @@ -48,8 +48,5 @@ - - - \ No newline at end of file diff --git a/resources/pom.xml b/resources/pom.xml index 1da503b..131e0f2 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -6,7 +6,7 @@ com.monkeyk resources - 0.3 + 2.0.0 ${project.artifactId} war @@ -17,6 +17,7 @@ 3.2.2.RELEASE 1.6.12 + ${project.version} 5.1.35 1.0.2 @@ -158,7 +159,7 @@ com.monkeyk core - 0.3 + ${core.version} -- Gitee From b6e0f5b7eff46541ceadb15b7024370d79d0bccf Mon Sep 17 00:00:00 2001 From: mkk Date: Sun, 12 Jul 2020 23:34:29 +0800 Subject: [PATCH 02/78] Update shiro version 1.5.3 --- README.md | 4 +++- authz/pom.xml | 2 +- core/pom.xml | 2 +- resources/pom.xml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0c84be4..4f54af7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ #oauth2-shiro +### 注意:当前版本处理开发中,不稳定... +--- 整合Apache OltuShiro. 提供一个轻量的OAUTH2应用框架. 并根据不同的应用场景提供不同的实现(如web场景,移动设备). @@ -27,7 +29,7 @@
oltu -- 1.0.2
- shiro -- 1.2.3 + shiro -- 1.5.3
MySQL -- 5.6
diff --git a/authz/pom.xml b/authz/pom.xml index 79aba5d..a9ed50c 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -21,7 +21,7 @@ 5.1.35 1.0.2 - 1.2.3 + 1.5.3 2.5.4 diff --git a/core/pom.xml b/core/pom.xml index f69e65a..bb9bf2c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -18,7 +18,7 @@ 3.2.2.RELEASE 1.0.2 - 1.2.3 + 1.5.3 diff --git a/resources/pom.xml b/resources/pom.xml index 131e0f2..731e6f5 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -21,7 +21,7 @@ 5.1.35 1.0.2 - 1.2.3 + 2.5.4 -- Gitee From f30f1446b413f5cb6d0d4cdbd40fa0e9c8b23741 Mon Sep 17 00:00:00 2001 From: mkk Date: Mon, 13 Jul 2020 00:38:50 +0800 Subject: [PATCH 03/78] Update spirng-boot 2.3.1, update testing use junit5(replace testng) --- README.md | 3 +- authz/pom.xml | 435 ++++++++++-------- .../java/com/monkeyk/os/AuthzApplication.java | 20 + .../com/monkeyk/os/AuthzApplicationTest.java | 21 + core/pom.xml | 31 +- .../os/infrastructure/ThreadLocalHolder.java | 4 +- .../os/domain/shared/GuidGeneratorTest.java | 17 +- .../os/infrastructure/DateUtilsTest.java | 5 +- .../monkeyk/os/infrastructure/Jose4JTest.java | 13 +- pom.xml | 5 +- resources/pom.xml | 410 +++++++++-------- .../com/monkeyk/os/ResourcesApplication.java | 18 + .../test/java/com/monkeyk/os/ContextTest.java | 4 +- .../monkeyk/os/ResourcesApplicationTest.java | 22 + .../os/domain/oauth/AccessTokenTest.java | 7 +- .../jdbc/OAuthRSJdbcRepositoryTest.java | 5 +- .../java/com/monkeyk/os/web/ShiroTest.java | 12 +- .../java/com/monkeyk/os/web/WebUtilsTest.java | 6 +- 18 files changed, 600 insertions(+), 438 deletions(-) create mode 100644 authz/src/main/java/com/monkeyk/os/AuthzApplication.java create mode 100644 authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java create mode 100644 resources/src/main/java/com/monkeyk/os/ResourcesApplication.java create mode 100644 resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java diff --git a/README.md b/README.md index 4f54af7..cfb4386 100644 --- a/README.md +++ b/README.md @@ -141,8 +141,9 @@

  1. 升级使用Spring Boot

  2. +
  3. 升级使用JDK 1.8, 日志框架升级使用log4j2

  4. (153) - 尝试添加并实现OIDC在 oauth2-shiro中

  5. -
  6. 升级shiro, oltu版本

  7. +
  8. 升级shiro到1.5.3, oltu版本无变化


diff --git a/authz/pom.xml b/authz/pom.xml index a9ed50c..b3a64f9 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -4,6 +4,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + com.monkeyk authz 2.0.0 @@ -13,16 +20,17 @@ oltu and shiro [AUTHZ] + 1.8 UTF-8 - 3.2.2.RELEASE - 1.6.12 + + ${project.version} - 5.1.35 + 1.0.2 1.5.3 - 2.5.4 + com.mysql.jdbc.Driver @@ -35,16 +43,215 @@ + + + + com.monkeyk + core + ${core.version} + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + javax.servlet.jsp + jsp-api + 2.1 + provided + + + + + + + + + + + opensymphony + sitemesh + 2.4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.authzserver + ${oltu.version} + + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.jwt + ${oltu.version} + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + mysql + mysql-connector-java + runtime + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + javax.servlet + jstl + 1.1.2 + + + taglibs + standard + 1.1.2 + compile + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + + + + + + + + + + authz + + org.springframework.boot + spring-boot-maven-plugin + + maven-compiler-plugin 3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} UTF-8 @@ -134,202 +341,26 @@ - - - src/main/resources - - **/* - - - - - - - - - - src/test/resources - - **/* - - - + + + + + + + + + + + + + + + + + + + + - - - - com.monkeyk - core - ${core.version} - - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.1 - provided - - - - - net.sf.ehcache - ehcache-web - 2.0.4 - - - - opensymphony - sitemesh - 2.4 - - - org.aspectj - aspectjrt - ${aspectj.version} - compile - - - org.aspectj - aspectjweaver - ${aspectj.version} - compile - - - - commons-dbcp - commons-dbcp - 1.4 - - - - commons-io - commons-io - 2.4 - - - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - ${oltu.version} - - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.jwt - ${oltu.version} - - - - - - - - - - - - - - org.springframework - spring-aop - ${spring.version} - - - - org.springframework - spring-tx - ${spring.version} - - - org.springframework - spring-expression - ${spring.version} - - - org.springframework - spring-web - ${spring.version} - - - org.springframework - spring-webmvc - ${spring.version} - - - - - mysql - mysql-connector-java - ${mysql.version} - compile - - - - - - log4j - log4j - 1.2.14 - compile - - - org.slf4j - slf4j-log4j12 - 1.7.5 - compile - - - - - javax.servlet - jstl - 1.1.2 - - - taglibs - standard - 1.1.2 - compile - - - - - - com.fasterxml.jackson.core - jackson-databind - ${fasterxml.jackson.version} - - - - - - org.springframework - spring-test - ${spring.version} - test - - - org.testng - testng - 6.1.1 - test - - - junit - junit - - - - - - \ No newline at end of file diff --git a/authz/src/main/java/com/monkeyk/os/AuthzApplication.java b/authz/src/main/java/com/monkeyk/os/AuthzApplication.java new file mode 100644 index 0000000..0088b80 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/AuthzApplication.java @@ -0,0 +1,20 @@ +package com.monkeyk.os; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@SpringBootApplication +public class AuthzApplication { + + + public static void main(String[] args) { + SpringApplication.run(AuthzApplication.class, args); + } + +} diff --git a/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java b/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java new file mode 100644 index 0000000..78b5d23 --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java @@ -0,0 +1,21 @@ +package com.monkeyk.os; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + */ +@SpringBootTest +class AuthzApplicationTest { + + + @Test + void contextLoads() { + } + +} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index bb9bf2c..ed530aa 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -14,9 +14,10 @@ oltu and shiro [CORE] + 1.8 UTF-8 - 3.2.2.RELEASE + 5.2.7.RELEASE 1.0.2 1.5.3 @@ -30,8 +31,8 @@ maven-compiler-plugin 3.2 - 1.8 - 1.8 + ${java.version} + ${java.version} UTF-8 @@ -130,17 +131,23 @@ ${spring.version} test + + + + + + + + + + + + - org.testng - testng - 6.1.1 + org.junit.jupiter + junit-jupiter + 5.6.2 test - - - junit - junit - - diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java b/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java index ed8a706..cba22ee 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java @@ -1,11 +1,13 @@ package com.monkeyk.os.infrastructure; +import org.springframework.core.NamedThreadLocal; + /** * @author Shengzhao Li */ public abstract class ThreadLocalHolder { - private static ThreadLocal clientIpThreadLocal = new ThreadLocal<>(); + private static NamedThreadLocal clientIpThreadLocal = new NamedThreadLocal<>("clientIpThreadLocal"); /* diff --git a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java index 22b2a6b..ed94697 100644 --- a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java +++ b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java @@ -1,8 +1,9 @@ package com.monkeyk.os.domain.shared; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /* * @author Shengzhao Li @@ -14,7 +15,7 @@ public class GuidGeneratorTest { final String clientId = GuidGenerator.generateClientId(); assertNotNull(clientId); assertTrue(clientId.length() == 20); - System.out.println(clientId); +// System.out.println(clientId); } @Test @@ -22,10 +23,10 @@ public class GuidGeneratorTest { final String clientSecret = GuidGenerator.generateClientSecret(); assertNotNull(clientSecret); assertTrue(clientSecret.length() == 20); - System.out.println(clientSecret); - - for (int i = 0; i < 5; i++) { - System.out.println(GuidGenerator.generateClientSecret()); - } +// System.out.println(clientSecret); +// +// for (int i = 0; i < 5; i++) { +// System.out.println(GuidGenerator.generateClientSecret()); +// } } } \ No newline at end of file diff --git a/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java b/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java index 4d63fff..dbde7d5 100644 --- a/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java +++ b/core/src/test/java/com/monkeyk/os/infrastructure/DateUtilsTest.java @@ -1,8 +1,9 @@ package com.monkeyk.os.infrastructure; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Shengzhao Li diff --git a/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java b/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java index 8a6501a..8875676 100644 --- a/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java +++ b/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java @@ -12,12 +12,13 @@ import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.keys.AesKey; import org.jose4j.keys.EllipticCurves; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; + import java.security.Key; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.*; + /** * 2016/12/15 @@ -132,7 +133,7 @@ public class Jose4JTest { .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by .setExpectedAudience("Audience") // to whom the JWT is intended for - //公钥 + //公钥 .setVerificationKey(jwk.getKey()) // verify the signature with the public key .build(); // create the JwtConsumer instance @@ -215,9 +216,9 @@ public class Jose4JTest { .setRequireSubject() // the JWT must have a subject claim .setExpectedIssuer("Issuer") // whom the JWT needs to have been issued by .setExpectedAudience("Audience") // to whom the JWT is intended for - //解密的私钥 + //解密的私钥 .setDecryptionKey(receiverJwk.getPrivateKey()) // decrypt with the receiver's private key - //验签的公钥 + //验签的公钥 .setVerificationKey(sendJwk.getPublicKey()) // verify the signature with the sender's public key .build(); // create the JwtConsumer instance diff --git a/pom.xml b/pom.xml index 6780aff..7d12923 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ oltu and shiro + 1.8 UTF-8 @@ -39,8 +40,8 @@ maven-compiler-plugin 3.2 - 1.8 - 1.8 + ${java.version} + ${java.version} UTF-8 diff --git a/resources/pom.xml b/resources/pom.xml index 731e6f5..fb0d16f 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -4,6 +4,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + com.monkeyk resources 2.0.0 @@ -13,16 +20,17 @@ oltu and shiro [RESOURCES] + 1.8 UTF-8 - 3.2.2.RELEASE - 1.6.12 + + ${project.version} - 5.1.35 + 1.0.2 - - 2.5.4 + + com.mysql.jdbc.Driver @@ -34,16 +42,201 @@ false + + + + com.monkeyk + core + ${core.version} + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + javax.servlet.jsp + jsp-api + 2.1 + provided + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.resourceserver + ${oltu.version} + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.resourceserver-filter + ${oltu.version} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-jdbc + + + org.springframework.boot + spring-boot-starter-web + + + mysql + mysql-connector-java + runtime + + + + + + + + + + + + + + + + + + + + + + + + + + + javax.servlet + jstl + 1.1.2 + + + taglibs + standard + 1.1.2 + compile + + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + + + + + + + + + + + + + + + + + rs + + org.springframework.boot + spring-boot-maven-plugin + + maven-compiler-plugin 3.2 - 1.7 - 1.7 + ${java.version} + ${java.version} UTF-8 @@ -133,190 +326,27 @@ - - - src/main/resources - - **/* - - - - - - - - - - src/test/resources - - **/* - - - + + + + + + + + + + + + + + + + + + + + - - - com.monkeyk - core - ${core.version} - - - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.1 - provided - - - - - org.aspectj - aspectjrt - ${aspectj.version} - compile - - - org.aspectj - aspectjweaver - ${aspectj.version} - compile - - - - commons-dbcp - commons-dbcp - 1.4 - - - - commons-io - commons-io - 2.4 - - - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver - ${oltu.version} - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.resourceserver-filter - ${oltu.version} - - - - - - - - - - - - - org.springframework - spring-aop - ${spring.version} - - - - - org.springframework - spring-tx - ${spring.version} - - - org.springframework - spring-expression - ${spring.version} - - - org.springframework - spring-web - ${spring.version} - - - org.springframework - spring-webmvc - ${spring.version} - - - - - mysql - mysql-connector-java - ${mysql.version} - compile - - - - - - log4j - log4j - 1.2.14 - compile - - - org.slf4j - slf4j-log4j12 - 1.7.5 - compile - - - - - javax.servlet - jstl - 1.1.2 - - - taglibs - standard - 1.1.2 - compile - - - - - - com.fasterxml.jackson.core - jackson-databind - ${fasterxml.jackson.version} - - - - - - org.springframework - spring-test - ${spring.version} - test - - - org.testng - testng - 6.1.1 - test - - - junit - junit - - - - - - \ No newline at end of file diff --git a/resources/src/main/java/com/monkeyk/os/ResourcesApplication.java b/resources/src/main/java/com/monkeyk/os/ResourcesApplication.java new file mode 100644 index 0000000..ae8d5e7 --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/ResourcesApplication.java @@ -0,0 +1,18 @@ +package com.monkeyk.os; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@SpringBootApplication +public class ResourcesApplication { + + public static void main(String[] args) { + SpringApplication.run(ResourcesApplication.class, args); + } +} diff --git a/resources/src/test/java/com/monkeyk/os/ContextTest.java b/resources/src/test/java/com/monkeyk/os/ContextTest.java index 8a6ac84..c13d13a 100644 --- a/resources/src/test/java/com/monkeyk/os/ContextTest.java +++ b/resources/src/test/java/com/monkeyk/os/ContextTest.java @@ -2,14 +2,14 @@ package com.monkeyk.os; import com.monkeyk.os.domain.shared.BeanProvider; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.BeforeTransaction; /** * @author Shengzhao Li */ @ContextConfiguration(locations = {"classpath:/spring/*.xml"}, initializers = {TestApplicationContextInitializer.class}) -public abstract class ContextTest extends AbstractTransactionalTestNGSpringContextTests { +public abstract class ContextTest extends AbstractTransactionalJUnit4SpringContextTests { @BeforeTransaction diff --git a/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java b/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java new file mode 100644 index 0000000..e3cc851 --- /dev/null +++ b/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java @@ -0,0 +1,22 @@ +package com.monkeyk.os; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2020/7/13 + * + * @author Shengzhao Li + */ +@SpringBootTest +class ResourcesApplicationTest { + + + @Test + void contextLoads() { + } + + +} \ No newline at end of file diff --git a/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java b/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java index 0ab9273..8929c1d 100644 --- a/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java +++ b/resources/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java @@ -1,13 +1,12 @@ package com.monkeyk.os.domain.oauth; import com.monkeyk.os.infrastructure.DateUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; -import java.net.URLDecoder; import java.net.URLEncoder; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * @author Shengzhao Li diff --git a/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java b/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java index 83c622c..2e57fa0 100644 --- a/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java +++ b/resources/src/test/java/com/monkeyk/os/infrastructure/jdbc/OAuthRSJdbcRepositoryTest.java @@ -5,10 +5,11 @@ import com.monkeyk.os.domain.oauth.AccessToken; import com.monkeyk.os.domain.oauth.ClientDetails; import com.monkeyk.os.domain.oauth.OauthCode; import com.monkeyk.os.domain.shared.GuidGenerator; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.assertNull; + /** * 15-6-13 diff --git a/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java b/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java index 322f648..22fdf77 100644 --- a/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java +++ b/resources/src/test/java/com/monkeyk/os/web/ShiroTest.java @@ -19,13 +19,15 @@ import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.Subject; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; import java.util.List; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * 15-6-10 @@ -35,7 +37,9 @@ import static org.testng.Assert.assertTrue; public class ShiroTest { - @Test(enabled = false) +// @Test(enabled = false) + @Test() + @Disabled() public void login() { String username = "abc"; //init SecurityManager diff --git a/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java b/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java index 7e38d21..df37a63 100644 --- a/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java +++ b/resources/src/test/java/com/monkeyk/os/web/WebUtilsTest.java @@ -1,11 +1,13 @@ package com.monkeyk.os.web; import org.apache.oltu.oauth2.common.utils.OAuthUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; + import java.util.Arrays; -import static org.testng.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class WebUtilsTest { -- Gitee From 7c2fe3ee526c16d8ea7dab598d7e7277d25067e3 Mon Sep 17 00:00:00 2001 From: mkk Date: Mon, 13 Jul 2020 00:40:40 +0800 Subject: [PATCH 04/78] Update use BeanFactory --- .../monkeyk/os/domain/shared/BeanProvider.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java b/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java index 167f914..4dc5a1d 100644 --- a/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java +++ b/core/src/main/java/com/monkeyk/os/domain/shared/BeanProvider.java @@ -1,5 +1,6 @@ package com.monkeyk.os.domain.shared; +import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationContext; /** @@ -9,11 +10,17 @@ public abstract class BeanProvider { private static ApplicationContext applicationContext; + /** + * @since 2.0.0 + */ + private static BeanFactory beanFactory; + private BeanProvider() { } public static void initialize(ApplicationContext applicationContext) { BeanProvider.applicationContext = applicationContext; + BeanProvider.beanFactory = applicationContext.getAutowireCapableBeanFactory(); } /** @@ -24,18 +31,19 @@ public abstract class BeanProvider { * @return Bean instance */ public static T getBean(Class clazz) { - if (applicationContext == null) { + if (beanFactory == null) { return null; } - return applicationContext.getBean(clazz); + return beanFactory.getBean(clazz); } @SuppressWarnings("unchecked") public static T getBean(String beanId) { - if (applicationContext == null) { + if (beanFactory == null) { return null; } - return (T) applicationContext.getBean(beanId); + return (T) beanFactory.getBean(beanId); } + } \ No newline at end of file -- Gitee From cca8e4f5c8c9905eb9a23c8056b15af8e309b677 Mon Sep 17 00:00:00 2001 From: mkk Date: Mon, 13 Jul 2020 00:47:18 +0800 Subject: [PATCH 05/78] Update spirng-boot 2.3.1, update testing use junit5(replace testng) --- authz/src/test/java/com/monkeyk/os/ContextTest.java | 4 ++-- .../com/monkeyk/os/domain/oauth/AccessTokenTest.java | 7 ++++--- .../domain/oauth/AuthenticationIdGeneratorTest.java | 7 ++++--- .../monkeyk/os/domain/users/PasswordHandlerTest.java | 5 +++-- .../infrastructure/jdbc/OauthJdbcRepositoryTest.java | 6 ++++-- .../jdbc/UsersJdbcAuthzRepositoryTest.java | 6 ++++-- .../src/test/java/com/monkeyk/os/web/ShiroTest.java | 12 ++++++++---- .../test/java/com/monkeyk/os/web/WebUtilsTest.java | 8 ++++---- core/pom.xml | 12 ++++++------ 9 files changed, 39 insertions(+), 28 deletions(-) diff --git a/authz/src/test/java/com/monkeyk/os/ContextTest.java b/authz/src/test/java/com/monkeyk/os/ContextTest.java index 8a6ac84..c13d13a 100644 --- a/authz/src/test/java/com/monkeyk/os/ContextTest.java +++ b/authz/src/test/java/com/monkeyk/os/ContextTest.java @@ -2,14 +2,14 @@ package com.monkeyk.os; import com.monkeyk.os.domain.shared.BeanProvider; import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; +import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.BeforeTransaction; /** * @author Shengzhao Li */ @ContextConfiguration(locations = {"classpath:/spring/*.xml"}, initializers = {TestApplicationContextInitializer.class}) -public abstract class ContextTest extends AbstractTransactionalTestNGSpringContextTests { +public abstract class ContextTest extends AbstractTransactionalJUnit4SpringContextTests { @BeforeTransaction diff --git a/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java b/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java index 0ab9273..75d22b0 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/oauth/AccessTokenTest.java @@ -1,13 +1,14 @@ package com.monkeyk.os.domain.oauth; import com.monkeyk.os.infrastructure.DateUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; + import java.net.URLDecoder; import java.net.URLEncoder; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * @author Shengzhao Li diff --git a/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java b/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java index 5b3bd96..a58d76e 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/oauth/AuthenticationIdGeneratorTest.java @@ -11,9 +11,10 @@ */ package com.monkeyk.os.domain.oauth; -import org.testng.annotations.Test; -import static org.testng.Assert.assertNotNull; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotNull; /** * 15-6-20 @@ -29,7 +30,7 @@ public class AuthenticationIdGeneratorTest { final String generate = generator.generate("clientid", "username", "authid"); assertNotNull(generate); - System.out.println(generate); +// System.out.println(generate); } } diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java index eeb1950..0ee1f5b 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/PasswordHandlerTest.java @@ -1,8 +1,9 @@ package com.monkeyk.os.domain.users; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /* * @author Shengzhao Li diff --git a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java index 5d32145..1ddde27 100644 --- a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java +++ b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepositoryTest.java @@ -7,12 +7,14 @@ import com.monkeyk.os.domain.oauth.OauthCode; import com.monkeyk.os.domain.shared.GuidGenerator; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.testng.annotations.Test; + import java.util.List; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.*; + /** * 15-6-13 diff --git a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java index 04b6406..5f8de74 100644 --- a/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java +++ b/authz/src/test/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepositoryTest.java @@ -15,12 +15,14 @@ import com.monkeyk.os.ContextTest; import com.monkeyk.os.domain.shared.GuidGenerator; import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; +import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.testng.annotations.Test; + import java.util.List; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.*; + /* * @author Shengzhao Li diff --git a/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java b/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java index 322f648..771b129 100644 --- a/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java +++ b/authz/src/test/java/com/monkeyk/os/web/ShiroTest.java @@ -19,13 +19,15 @@ import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.SimpleAccountRealm; import org.apache.shiro.subject.Subject; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + import java.util.ArrayList; import java.util.List; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; + /** * 15-6-10 @@ -35,7 +37,9 @@ import static org.testng.Assert.assertTrue; public class ShiroTest { - @Test(enabled = false) +// @Test(enabled = false) + @Test + @Disabled public void login() { String username = "abc"; //init SecurityManager diff --git a/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java b/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java index 7e38d21..1cecf5c 100644 --- a/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java +++ b/authz/src/test/java/com/monkeyk/os/web/WebUtilsTest.java @@ -1,11 +1,11 @@ package com.monkeyk.os.web; import org.apache.oltu.oauth2.common.utils.OAuthUtils; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; -import java.util.Arrays; -import static org.testng.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class WebUtilsTest { @@ -18,7 +18,7 @@ public class WebUtilsTest { final String[] strings = OAuthUtils.decodeClientAuthenticationHeader(text); assertNotNull(strings); - System.out.println(Arrays.toString(strings)); +// System.out.println(Arrays.toString(strings)); } } \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index ed530aa..709fece 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -117,12 +117,12 @@ - - org.slf4j - slf4j-log4j12 - 1.7.5 - compile - + + + + + + -- Gitee From 52f0027cba3094178b2bd37fa9aa89c94a283e2f Mon Sep 17 00:00:00 2001 From: mkk Date: Wed, 15 Jul 2020 00:20:53 +0800 Subject: [PATCH 06/78] Refactor use spring-boot flow --- authz/pom.xml | 76 +++++--- .../monkeyk/os/config/AuthzContextConfig.java | 66 +++++++ .../os/config/AuthzSecurityConfig.java | 136 ++++++++++++++ .../com/monkeyk/os/config/AuthzWebConfig.java | 85 +++++++++ .../os/web/context/BeanContextAware.java | 23 +++ .../context/BeanContextLoaderListener.java | 2 + .../src/main/resources/application.properties | 30 ++++ authz/src/main/resources/authz.properties | 11 -- authz/src/main/resources/banner.txt | 10 ++ authz/src/main/resources/log4j2.xml | 50 ++++++ ...ging.properties => logging.properties.old} | 0 ...uthz-context.xml => authz-context.xml.old} | 0 ...hz-security.xml => authz-security.xml.old} | 0 .../static}/OS_API-0.2.html | 0 .../static}/OS_API-0.3.html | 0 .../static}/angular.min.js | 0 .../static}/bootstrap.min.css | 0 .../{webapp => resources/static}/favicon.ico | Bin .../static}/oauth_test.html | 0 .../resources => resources/static}/readme.txt | 0 .../templates}/comm-header.jsp | 0 .../templates}/decorators/main.jsp | 0 .../jsp => resources/templates}/index.jsp | 0 .../jsp => resources/templates}/login.jsp | 0 .../templates}/oauth/client_details.jsp | 0 .../templates}/oauth/client_details_plus.jsp | 0 .../templates}/oauth/oauth_approval.jsp | 0 .../templates}/oauth/oauth_login.jsp | 0 .../templates}/oauth/test_client.jsp | 0 .../templates}/unauthorized.jsp | 0 .../templates}/users/user_plus.jsp | 0 .../templates}/users/users_overview.jsp | 0 authz/src/main/webapp/WEB-INF/decorators.xml | 18 -- authz/src/main/webapp/WEB-INF/log4j.xml | 29 --- authz/src/main/webapp/WEB-INF/mkk-servlet.xml | 63 ------- authz/src/main/webapp/WEB-INF/web.xml | 169 ------------------ authz/src/main/webapp/loading.jsp | 9 - 37 files changed, 451 insertions(+), 326 deletions(-) create mode 100644 authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java create mode 100644 authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java create mode 100644 authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java create mode 100644 authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java create mode 100644 authz/src/main/resources/application.properties delete mode 100644 authz/src/main/resources/authz.properties create mode 100644 authz/src/main/resources/banner.txt create mode 100644 authz/src/main/resources/log4j2.xml rename authz/src/main/resources/{logging.properties => logging.properties.old} (100%) rename authz/src/main/resources/spring/{authz-context.xml => authz-context.xml.old} (100%) rename authz/src/main/resources/spring/{authz-security.xml => authz-security.xml.old} (100%) rename authz/src/main/{webapp/resources => resources/static}/OS_API-0.2.html (100%) rename authz/src/main/{webapp/resources => resources/static}/OS_API-0.3.html (100%) rename authz/src/main/{webapp/resources => resources/static}/angular.min.js (100%) rename authz/src/main/{webapp/resources => resources/static}/bootstrap.min.css (100%) rename authz/src/main/{webapp => resources/static}/favicon.ico (100%) rename authz/src/main/{webapp/resources => resources/static}/oauth_test.html (100%) rename authz/src/main/{webapp/resources => resources/static}/readme.txt (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/comm-header.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/decorators/main.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/index.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/login.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/oauth/client_details.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/oauth/client_details_plus.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/oauth/oauth_approval.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/oauth/oauth_login.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/oauth/test_client.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/unauthorized.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/users/user_plus.jsp (100%) rename authz/src/main/{webapp/WEB-INF/jsp => resources/templates}/users/users_overview.jsp (100%) delete mode 100644 authz/src/main/webapp/WEB-INF/decorators.xml delete mode 100644 authz/src/main/webapp/WEB-INF/log4j.xml delete mode 100644 authz/src/main/webapp/WEB-INF/mkk-servlet.xml delete mode 100644 authz/src/main/webapp/WEB-INF/web.xml delete mode 100644 authz/src/main/webapp/loading.jsp diff --git a/authz/pom.xml b/authz/pom.xml index b3a64f9..6649bd9 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -16,7 +16,7 @@ 2.0.0 ${project.artifactId} - war + jar oltu and shiro [AUTHZ] @@ -51,19 +51,19 @@ ${core.version} - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.1 - provided - + + + + + + + + + + + + + @@ -125,6 +125,22 @@ + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + + org.springframework.boot + spring-boot-starter-log4j2 + + org.springframework.boot spring-boot-starter-jdbc @@ -138,6 +154,12 @@ mysql-connector-java runtime + + + com.zaxxer + HikariCP + + @@ -189,17 +211,17 @@ - - javax.servlet - jstl - 1.1.2 - - - taglibs - standard - 1.1.2 - compile - + + + + + + + + + + + @@ -248,7 +270,7 @@ maven-compiler-plugin - 3.2 + ${java.version} ${java.version} @@ -258,7 +280,7 @@ maven-war-plugin - 2.6 + */classes/authz.properties */classes/authz.properties diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java new file mode 100644 index 0000000..9078716 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java @@ -0,0 +1,66 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.domain.oauth.AuthenticationIdGenerator; +import com.monkeyk.os.domain.oauth.DefaultAuthenticationIdGenerator; +import org.apache.oltu.oauth2.as.issuer.MD5Generator; +import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; +import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.TransactionManager; + +import javax.sql.DataSource; + +/** + * 2020/7/14 + *

+ * Replace authz-context.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class AuthzContextConfig { + + + /** + * 事务配置 + */ + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + return transactionManager; + } + + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + + /** + * AuthenticationId 的生成器 + */ + @Bean + public AuthenticationIdGenerator authenticationIdGenerator() { + return new DefaultAuthenticationIdGenerator(); + } + + /** + * 使用MD5 OAuthIssuer, 生成随机值,如 access_token, refresh_token + * 可根据需要扩展使用其他的实现 + */ + @Bean + public OAuthIssuer oAuthIssuer() { + MD5Generator md5Generator = new MD5Generator(); + return new OAuthIssuerImpl(md5Generator); + } + + +} diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java new file mode 100644 index 0000000..3449eee --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -0,0 +1,136 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.infrastructure.shiro.MkkJdbcRealm; +import org.apache.shiro.authc.credential.CredentialsMatcher; +import org.apache.shiro.authc.credential.HashedCredentialsMatcher; +import org.apache.shiro.cache.AbstractCacheManager; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.AuthorizingRealm; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +/** + * 2020/7/14 + *

+ * Replace authz-security.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class AuthzSecurityConfig { + + /* + * 这是一个标准的 SHIRO 安全配置 + 注意OAuth的URL配置: /oauth/** = anon + * + * */ + + + /** + * 使用MD5 进行密码的加密 + */ + @Bean + public CredentialsMatcher credentialsMatcher() { + HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); + credentialsMatcher.setHashAlgorithmName("MD5"); +// credentialsMatcher.setStoredCredentialsHexEncoded(false); + return credentialsMatcher; + } + + /** + * 扩展的 SHIRO Realm + * 使用JDBC实现, 并添加 逻辑删除 (archived = 0) 的处理 + */ + @Bean + public AuthorizingRealm jdbcRealm(DataSource dataSource) { + MkkJdbcRealm realm = new MkkJdbcRealm(); + realm.setName("jdbcRealm"); + realm.setDataSource(dataSource); + realm.setCredentialsMatcher(credentialsMatcher()); + realm.setPermissionsLookupEnabled(true); + return realm; + } + + /** + * 使用基于内存的缓存 SHIRO 相关数据 + */ + @Bean + public AbstractCacheManager shiroCacheManager() { + return new MemoryConstrainedCacheManager(); + } + + /** + * SHIRO SecurityManager 配置 + */ + @Bean + public DefaultWebSecurityManager securityManager(Realm realm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + securityManager.setCacheManager(shiroCacheManager()); + return securityManager; + } + + + /** + * SHIRO安全机制拦截器 Filter实现, 注意id必须与 web.xml 中的 shiroFilter 一致 + */ + @Bean + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); + factoryBean.setSecurityManager(securityManager); + factoryBean.setLoginUrl("/login"); + factoryBean.setSuccessUrl("/index"); + factoryBean.setUnauthorizedUrl("/unauthorized"); + //权限控制 + Map map = new HashMap<>(); + map.put("/favicon.ico", "anon"); + map.put("/static/**", "anon"); + map.put("/resources/**", "anon"); + map.put("/login", "anon"); + map.put("/unauthorized", "anon"); + // # OAuth anon + map.put("/oauth/**", "anon"); + map.put("/users/**", "anon"); + map.put("/client_details*", "anon"); + map.put("/client_details/**", "anon"); + map.put("/logout", "logout"); + //# admin role + map.put("/admin/**", "authc, roles[\"Admin\"]"); + // #user permissions + map.put("/user/list", "authc, perms[\"user:list\"]"); + map.put("/user/create", "authc, perms[\"user:create\"]"); + // # everything else requires authentication: + map.put("/**", "authc"); + factoryBean.setFilterChainDefinitionMap(map); +// factoryBean.setFilterChainDefinitions(" /favicon.ico = anon\n" + +// " /resources/** = anon\n" + +// " /statics/** = anon\n" + +// " /login = anon\n" + +// " /unauthorized = anon\n" + +// " # OAuth anon\n" + +// " /oauth/** = anon\n" + +// " /users/** = anon\n" + +// " /client_details* = anon\n" + +// " /client_details/** = anon\n" + +// " /logout = logout\n" + +// " # admin role\n" + +// " /admin/** = authc, roles[\"Admin\"]\n" + +// " #user permissions\n" + +// " /user/list = authc, perms[\"user:list\"]\n" + +// " /user/create = authc, perms[\"user:create\"]\n" + +// " # everything else requires authentication:\n" + +// " /** = authc"); + return factoryBean; + } + + +} diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java new file mode 100644 index 0000000..285d78a --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java @@ -0,0 +1,85 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.web.context.MkkCharacterEncodingFilter; +import com.monkeyk.os.web.context.OAuthShiroHandlerExceptionResolver; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * 2020/7/14 + *

+ * Replace web.xml + * mkk-servlet.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class AuthzWebConfig { + + + /** + * 字符编码配置 UTF-8 + */ + @Bean + public FilterRegistrationBean encodingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MkkCharacterEncodingFilter()); + registrationBean.addUrlPatterns("/*"); + //值越小越靠前 + registrationBean.setOrder(1); + return registrationBean; + } + + +// /** +// Shiro Filter +// SHIRO 安全过滤器 配置 +// */ +// @Bean +// public DelegatingFilterProxy shiroFilter(){ +// DelegatingFilterProxy filterProxy=new DelegatingFilterProxy(); +// filterProxy.setTargetFilterLifecycle(true); +// +// return filterProxy; +// } + + + /** + * 异常处理配置 + */ + @Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new OAuthShiroHandlerExceptionResolver(); + } + + /** + * Shiro AOP + */ + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /** + * Enable Shiro Annotations for Spring-configured beans. Only run after + * the lifecycleBeanProcessor has run: + */ +// public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ +// DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); +// autoProxyCre +// return autoProxyCreator; +// } + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + sourceAdvisor.setSecurityManager(securityManager); + return sourceAdvisor; + } + +} diff --git a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java new file mode 100644 index 0000000..8e6b6af --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -0,0 +1,23 @@ +package com.monkeyk.os.web.context; + +import com.monkeyk.os.domain.shared.BeanProvider; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * 2020/7/14 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class BeanContextAware implements ApplicationContextAware { + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + BeanProvider.initialize(applicationContext); + } +} diff --git a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java index c59a2ee..7c1af04 100644 --- a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java +++ b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java @@ -9,7 +9,9 @@ import javax.servlet.ServletContextEvent; /** * @author Shengzhao Li + * @deprecated Use BeanContextAware.java from 2.0.0 */ +@Deprecated public class BeanContextLoaderListener extends ContextLoaderListener { diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties new file mode 100644 index 0000000..7cd464b --- /dev/null +++ b/authz/src/main/resources/application.properties @@ -0,0 +1,30 @@ +# authz properties +# +spring.application.name=AuthZ +# +# +# Support deploy to a servlet-container +spring.jmx.enabled=false +#Override default +spring.main.allow-bean-definition-overriding=true +# +#JDBC +#Connection +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# Logging +# +logging.config=classpath:log4j2.xml +# +# +# MVC +spring.mvc.ignore-default-model-on-redirect=false +#spring.mvc.locale=zh_CN +spring.mvc.view.prefix=/templates/ +spring.mvc.view.suffix=.jsp +# diff --git a/authz/src/main/resources/authz.properties b/authz/src/main/resources/authz.properties deleted file mode 100644 index ed4fc6a..0000000 --- a/authz/src/main/resources/authz.properties +++ /dev/null @@ -1,11 +0,0 @@ - -# ݿ -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# localhost -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - diff --git a/authz/src/main/resources/banner.txt b/authz/src/main/resources/banner.txt new file mode 100644 index 0000000..8a6aa6a --- /dev/null +++ b/authz/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +${AnsiColor.BRIGHT_GREEN} + ____ _ _____ _ ____ +/ _ \/ \ /\/__ __\/ \ /|/_ \ +| / \|| | || / \ | |_|| / / +| |-||| \_/| | | | | ||/ /_ +\_/ \|\____/ \_/ \_/ \|\____/ + + +AuthZ: ${application.formatted-version} +Spring Boot: ${spring-boot.formatted-version} diff --git a/authz/src/main/resources/log4j2.xml b/authz/src/main/resources/log4j2.xml new file mode 100644 index 0000000..8798675 --- /dev/null +++ b/authz/src/main/resources/log4j2.xml @@ -0,0 +1,50 @@ + + + + + + ../logs/ + ???? + %xwEx + %5p + %clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + %d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/authz/src/main/resources/logging.properties b/authz/src/main/resources/logging.properties.old similarity index 100% rename from authz/src/main/resources/logging.properties rename to authz/src/main/resources/logging.properties.old diff --git a/authz/src/main/resources/spring/authz-context.xml b/authz/src/main/resources/spring/authz-context.xml.old similarity index 100% rename from authz/src/main/resources/spring/authz-context.xml rename to authz/src/main/resources/spring/authz-context.xml.old diff --git a/authz/src/main/resources/spring/authz-security.xml b/authz/src/main/resources/spring/authz-security.xml.old similarity index 100% rename from authz/src/main/resources/spring/authz-security.xml rename to authz/src/main/resources/spring/authz-security.xml.old diff --git a/authz/src/main/webapp/resources/OS_API-0.2.html b/authz/src/main/resources/static/OS_API-0.2.html similarity index 100% rename from authz/src/main/webapp/resources/OS_API-0.2.html rename to authz/src/main/resources/static/OS_API-0.2.html diff --git a/authz/src/main/webapp/resources/OS_API-0.3.html b/authz/src/main/resources/static/OS_API-0.3.html similarity index 100% rename from authz/src/main/webapp/resources/OS_API-0.3.html rename to authz/src/main/resources/static/OS_API-0.3.html diff --git a/authz/src/main/webapp/resources/angular.min.js b/authz/src/main/resources/static/angular.min.js similarity index 100% rename from authz/src/main/webapp/resources/angular.min.js rename to authz/src/main/resources/static/angular.min.js diff --git a/authz/src/main/webapp/resources/bootstrap.min.css b/authz/src/main/resources/static/bootstrap.min.css similarity index 100% rename from authz/src/main/webapp/resources/bootstrap.min.css rename to authz/src/main/resources/static/bootstrap.min.css diff --git a/authz/src/main/webapp/favicon.ico b/authz/src/main/resources/static/favicon.ico similarity index 100% rename from authz/src/main/webapp/favicon.ico rename to authz/src/main/resources/static/favicon.ico diff --git a/authz/src/main/webapp/resources/oauth_test.html b/authz/src/main/resources/static/oauth_test.html similarity index 100% rename from authz/src/main/webapp/resources/oauth_test.html rename to authz/src/main/resources/static/oauth_test.html diff --git a/authz/src/main/webapp/resources/readme.txt b/authz/src/main/resources/static/readme.txt similarity index 100% rename from authz/src/main/webapp/resources/readme.txt rename to authz/src/main/resources/static/readme.txt diff --git a/authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp b/authz/src/main/resources/templates/comm-header.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp rename to authz/src/main/resources/templates/comm-header.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp b/authz/src/main/resources/templates/decorators/main.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp rename to authz/src/main/resources/templates/decorators/main.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/index.jsp b/authz/src/main/resources/templates/index.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/index.jsp rename to authz/src/main/resources/templates/index.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/login.jsp b/authz/src/main/resources/templates/login.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/login.jsp rename to authz/src/main/resources/templates/login.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp b/authz/src/main/resources/templates/oauth/client_details.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp rename to authz/src/main/resources/templates/oauth/client_details.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp b/authz/src/main/resources/templates/oauth/client_details_plus.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp rename to authz/src/main/resources/templates/oauth/client_details_plus.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp b/authz/src/main/resources/templates/oauth/oauth_approval.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp rename to authz/src/main/resources/templates/oauth/oauth_approval.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp b/authz/src/main/resources/templates/oauth/oauth_login.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp rename to authz/src/main/resources/templates/oauth/oauth_login.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp b/authz/src/main/resources/templates/oauth/test_client.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp rename to authz/src/main/resources/templates/oauth/test_client.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp b/authz/src/main/resources/templates/unauthorized.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp rename to authz/src/main/resources/templates/unauthorized.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp b/authz/src/main/resources/templates/users/user_plus.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp rename to authz/src/main/resources/templates/users/user_plus.jsp diff --git a/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp b/authz/src/main/resources/templates/users/users_overview.jsp similarity index 100% rename from authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp rename to authz/src/main/resources/templates/users/users_overview.jsp diff --git a/authz/src/main/webapp/WEB-INF/decorators.xml b/authz/src/main/webapp/WEB-INF/decorators.xml deleted file mode 100644 index 1fa1456..0000000 --- a/authz/src/main/webapp/WEB-INF/decorators.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - /* - - - - - /resources/** - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/log4j.xml b/authz/src/main/webapp/WEB-INF/log4j.xml deleted file mode 100644 index 8f19edd..0000000 --- a/authz/src/main/webapp/WEB-INF/log4j.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml b/authz/src/main/webapp/WEB-INF/mkk-servlet.xml deleted file mode 100644 index ce7dffb..0000000 --- a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/web.xml b/authz/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 3c11874..0000000 --- a/authz/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - oauth2-shiro - - - - - - - webAppRootKey - oauth2-shiro - - - - - encodingFilter - com.monkeyk.os.web.context.MkkCharacterEncodingFilter - - encoding - UTF-8 - - - forceEncoding - true - - - - encodingFilter - /* - - - - - gzipFilter - - net.sf.ehcache.constructs.web.filter.GzipFilter - - - - gzipFilter - *.css - - - gzipFilter - *.png - - - gzipFilter - *.gif - - - gzipFilter - *.html - - - gzipFilter - *.js - - - - - - shiroFilter - org.springframework.web.filter.DelegatingFilterProxy - - targetFilterLifecycle - true - - - - - shiroFilter - /* - - - - - - sitemesh - com.opensymphony.sitemesh.webapp.SiteMeshFilter - - - sitemesh - /* - - - - ico - image/vnd.microsoft.icon - - - - - contextConfigLocation - classpath:spring/*.xml - - - - - log4jConfigLocation - /WEB-INF/log4j.xml - - - org.springframework.web.util.Log4jConfigListener - - - - - - com.monkeyk.os.web.context.BeanContextLoaderListener - - - - - mkk - org.springframework.web.servlet.DispatcherServlet - 2 - - - mkk - / - - - - - - - - - - - 30 - - - - - loading.jsp - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/loading.jsp b/authz/src/main/webapp/loading.jsp deleted file mode 100644 index 817b6df..0000000 --- a/authz/src/main/webapp/loading.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<% - request.getRequestDispatcher("index").forward(request, response); -%> \ No newline at end of file -- Gitee From 47fbca2580eed2a0ee9fae5e669358b934eb9046 Mon Sep 17 00:00:00 2001 From: mkk Date: Wed, 15 Jul 2020 00:32:23 +0800 Subject: [PATCH 07/78] Refactor use spring-boot flow --- authz/pom.xml | 46 +++++++++++-------- .../src/main/resources/application.properties | 3 -- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/authz/pom.xml b/authz/pom.xml index 6649bd9..c8c5a06 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -53,16 +53,16 @@ - - - - + + + + - - - - + + + + @@ -72,11 +72,11 @@ - - opensymphony - sitemesh - 2.4 - + + + + + @@ -149,6 +149,12 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-thymeleaf + + mysql mysql-connector-java @@ -212,15 +218,15 @@ - - - + + + - - - - + + + + diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 7cd464b..0d19152 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -24,7 +24,4 @@ logging.config=classpath:log4j2.xml # # MVC spring.mvc.ignore-default-model-on-redirect=false -#spring.mvc.locale=zh_CN -spring.mvc.view.prefix=/templates/ -spring.mvc.view.suffix=.jsp # -- Gitee From f45aa6a44f039956f9e06b357c070faa599c8ec0 Mon Sep 17 00:00:00 2001 From: monkeyk7 Date: Tue, 27 Apr 2021 18:52:33 +0800 Subject: [PATCH 08/78] update README.md. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfb4386..5be1be8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ #oauth2-shiro -### 注意:当前版本处理开发中,不稳定... +### 注意:当前版本处于开发中,不稳定... --- 整合Apache OltuShiro. 提供一个轻量的OAUTH2应用框架. -- Gitee From 88233453c7bbeda5b2faf5920868cf9085acfbec Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 8 Feb 2023 23:39:57 +0800 Subject: [PATCH 09/78] fix shiro issue, upgrade version 1.11.0 --- README.md | 2 +- authz/pom.xml | 2 +- core/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5be1be8..4554937 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@
oltu -- 1.0.2
- shiro -- 1.5.3 + shiro -- 1.11.0
MySQL -- 5.6 diff --git a/authz/pom.xml b/authz/pom.xml index c8c5a06..2ad9195 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -29,7 +29,7 @@ 1.0.2 - 1.5.3 + 1.11.0 diff --git a/core/pom.xml b/core/pom.xml index 709fece..6655e72 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -19,7 +19,7 @@ 5.2.7.RELEASE 1.0.2 - 1.5.3 + 1.11.0 -- Gitee From 4601ca4641bd9d8aa26db6962da5c6b4928250e3 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Fri, 15 Sep 2023 11:51:27 +0800 Subject: [PATCH 10/78] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4554937..975267d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#oauth2-shiro +## oauth2-shiro ### 注意:当前版本处于开发中,不稳定... -- Gitee From 6b628cf59493a14930cc0dbfa44e03162e87eac5 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Fri, 15 Sep 2023 11:53:57 +0800 Subject: [PATCH 11/78] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 975267d..d3024f6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 并根据不同的应用场景提供不同的实现(如web场景,移动设备). 该项目与spring-oauth-server实现相同的需求与场合. -只是在实现上使用的技术不同(spring-oauth-server使用Spring Security + spring-security-oauth2实现; oauth2-oltu实现); +只是在实现上使用的技术不同(spring-oauth-server使用Spring Security + spring-security-oauth2实现); 相比spring-oauth-server, oauth2-oltu具有如下特点:

-- Gitee From 1d294c197adad032b2b29754cf93397610ac5c2c Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 18 Sep 2023 15:19:16 +0800 Subject: [PATCH 12/78] =?UTF-8?q?=E7=BB=9F=E4=B8=80springboot=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=EF=BC=8C2.4.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- authz/pom.xml | 271 ++++------------------------------------------ core/pom.xml | 2 +- resources/pom.xml | 206 ++++++----------------------------- 4 files changed, 57 insertions(+), 424 deletions(-) diff --git a/README.md b/README.md index d3024f6..3a2632f 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@

主要技术及版本

- Spring Boot-- 2.3.1.RELEASE + Spring Boot-- 2.4.13
oltu -- 1.0.2
diff --git a/authz/pom.xml b/authz/pom.xml index 2ad9195..75b37e4 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.1.RELEASE + 2.4.13 @@ -33,7 +33,7 @@ - com.mysql.jdbc.Driver + com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&useUnicode=true&characterEncoding=utf8 andaily andaily @@ -51,56 +51,6 @@ ${core.version} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -116,14 +66,6 @@ - - - - - - - - org.springframework.boot @@ -135,11 +77,6 @@ - - - org.springframework.boot - spring-boot-starter-log4j2 - org.springframework.boot @@ -158,7 +95,6 @@ mysql mysql-connector-java - runtime @@ -166,77 +102,6 @@ HikariCP - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -250,18 +115,6 @@ - - - - - - - - - - - - @@ -275,120 +128,38 @@ - maven-compiler-plugin - - - ${java.version} - ${java.version} - UTF-8 - - - - - maven-war-plugin - + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + + revision + + + - */classes/authz.properties - */classes/authz.properties - - false - - ${project.version} - monkeyk.com - monkeyk.com - ${project.name} - ${project.version} - - + true + true + + ^git.remote.origin.url$ + ^git.branch$ + ^git.commit.id$ + ^git.build.time$ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - maven-surefire-plugin - 2.4 + ${test.skip} none - - **/*Test.java - - - - jdbc.url - ${jdbc.url} - - - jdbc.username - ${jdbc.user} - - - jdbc.password - ${jdbc.pass} - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml index 6655e72..a8ef56a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -17,7 +17,7 @@ 1.8 UTF-8 - 5.2.7.RELEASE + 5.3.13 1.0.2 1.11.0 diff --git a/resources/pom.xml b/resources/pom.xml index fb0d16f..157f6ed 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -7,7 +7,7 @@ org.springframework.boot spring-boot-starter-parent - 2.3.1.RELEASE + 2.4.13 @@ -16,7 +16,7 @@ 2.0.0 ${project.artifactId} - war + jar oltu and shiro [RESOURCES] @@ -50,19 +50,19 @@ ${core.version} - - - javax.servlet - javax.servlet-api - 3.1.0 - provided - - - javax.servlet.jsp - jsp-api - 2.1 - provided - + + + + + + + + + + + + + @@ -149,53 +149,9 @@ mysql mysql-connector-java - runtime - - - - - - - - - - - - - - - - - - - - - - - - - - - javax.servlet - jstl - 1.1.2 - - - taglibs - standard - 1.1.2 - compile - - - - - - - - org.springframework.boot @@ -208,18 +164,6 @@ - - - - - - - - - - - - @@ -232,120 +176,38 @@ - maven-compiler-plugin - 3.2 - - ${java.version} - ${java.version} - UTF-8 - - - - - maven-war-plugin - 2.6 + pl.project13.maven + git-commit-id-plugin + 4.9.10 + + + + revision + + + - */classes/resources.properties - */classes/resources.properties - - false - - ${project.version} - monkeyk.com - monkeyk.com - ${project.name} - ${project.version} - - + true + true + + ^git.remote.origin.url$ + ^git.branch$ + ^git.commit.id$ + ^git.build.time$ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - maven-surefire-plugin - 2.4 + ${test.skip} none - - **/*Test.java - - - - jdbc.url - ${jdbc.url} - - - jdbc.username - ${jdbc.user} - - - jdbc.password - ${jdbc.pass} - - - - - - - - - - - - - - - - - - - - - - -- Gitee From a209fb3db546b5484c4181bf750600c81ac9665b Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 18 Sep 2023 17:30:17 +0800 Subject: [PATCH 13/78] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=90=84=E7=B1=BB?= =?UTF-8?q?=E9=85=8D=E7=BD=AE,logback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- authz/pom.xml | 6 -- .../src/main/resources/application.properties | 2 +- authz/src/main/resources/log4j2.xml | 50 ------------- authz/src/main/resources/logback.xml | 47 ++++++++++++ .../com/monkeyk/os/AuthzApplicationTest.java | 1 + resources/pom.xml | 74 ------------------- 7 files changed, 50 insertions(+), 132 deletions(-) delete mode 100644 authz/src/main/resources/log4j2.xml create mode 100644 authz/src/main/resources/logback.xml diff --git a/README.md b/README.md index 3a2632f..3ef353f 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@

  1. 升级使用Spring Boot

  2. -
  3. 升级使用JDK 1.8, 日志框架升级使用log4j2

  4. +
  5. 升级使用JDK 1.8, 日志框架升级使用logback

  6. (153) - 尝试添加并实现OIDC在 oauth2-shiro中

  7. 升级shiro到1.5.3, oltu版本无变化

diff --git a/authz/pom.xml b/authz/pom.xml index 75b37e4..e52c611 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -70,12 +70,6 @@ org.springframework.boot spring-boot-starter - - - org.springframework.boot - spring-boot-starter-logging - - diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 0d19152..369db41 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -19,7 +19,7 @@ spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.hikari.maximum-pool-size=20 # Logging # -logging.config=classpath:log4j2.xml +#logging.config=classpath:log4j2.xml # # # MVC diff --git a/authz/src/main/resources/log4j2.xml b/authz/src/main/resources/log4j2.xml deleted file mode 100644 index 8798675..0000000 --- a/authz/src/main/resources/log4j2.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - ../logs/ - ???? - %xwEx - %5p - %clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - %d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/resources/logback.xml b/authz/src/main/resources/logback.xml new file mode 100644 index 0000000..dbcce63 --- /dev/null +++ b/authz/src/main/resources/logback.xml @@ -0,0 +1,47 @@ + + + ${spring.application.name} + + + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + true + + + logs/%d{yyyy-MM-dd}/authz-%i.log + 10MB + 15 + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java b/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java index 78b5d23..9bf6333 100644 --- a/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java +++ b/authz/src/test/java/com/monkeyk/os/AuthzApplicationTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.*; * 2020/7/13 * * @author Shengzhao Li + * @since 2.0.0 */ @SpringBootTest class AuthzApplicationTest { diff --git a/resources/pom.xml b/resources/pom.xml index 157f6ed..b27dcb6 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -50,45 +50,6 @@ ${core.version} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -103,41 +64,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.springframework.boot spring-boot-starter-jdbc -- Gitee From adbee8ecc5d1fbb21a718723fd04321595e83f87 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 18 Sep 2023 18:11:16 +0800 Subject: [PATCH 14/78] =?UTF-8?q?web,=20jsp/servlet=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- authz/pom.xml | 24 ++- .../os/web/controller/ShiroController.java | 2 +- .../src/main/resources/application.properties | 2 + .../main/webapp/WEB-INF/decorators.xml.old | 18 ++ .../WEB-INF/jsp}/comm-header.jsp | 0 .../WEB-INF/jsp}/decorators/main.jsp | 2 +- .../WEB-INF/jsp}/index.jsp | 0 .../WEB-INF/jsp}/login.jsp | 0 .../WEB-INF/jsp}/oauth/client_details.jsp | 0 .../jsp}/oauth/client_details_plus.jsp | 0 .../WEB-INF/jsp}/oauth/oauth_approval.jsp | 0 .../WEB-INF/jsp}/oauth/oauth_login.jsp | 0 .../WEB-INF/jsp}/oauth/test_client.jsp | 0 .../WEB-INF/jsp}/unauthorized.jsp | 0 .../WEB-INF/jsp}/users/user_plus.jsp | 0 .../WEB-INF/jsp}/users/users_overview.jsp | 0 authz/src/main/webapp/WEB-INF/log4j.xml.old | 29 +++ .../main/webapp/WEB-INF/mkk-servlet.xml.old | 63 +++++++ authz/src/main/webapp/WEB-INF/web.xml.old | 169 ++++++++++++++++++ authz/src/main/webapp/favicon.ico | Bin 0 -> 1150 bytes authz/src/main/webapp/loading.jsp | 9 + .../resources}/OS_API-0.2.html | 0 .../resources}/OS_API-0.3.html | 0 .../resources}/angular.min.js | 0 .../resources}/bootstrap.min.css | 0 .../resources}/oauth_test.html | 0 .../static => webapp/resources}/readme.txt | 0 27 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 authz/src/main/webapp/WEB-INF/decorators.xml.old rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/comm-header.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/decorators/main.jsp (91%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/index.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/login.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/oauth/client_details.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/oauth/client_details_plus.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/oauth/oauth_approval.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/oauth/oauth_login.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/oauth/test_client.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/unauthorized.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/users/user_plus.jsp (100%) rename authz/src/main/{resources/templates => webapp/WEB-INF/jsp}/users/users_overview.jsp (100%) create mode 100644 authz/src/main/webapp/WEB-INF/log4j.xml.old create mode 100644 authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old create mode 100644 authz/src/main/webapp/WEB-INF/web.xml.old create mode 100644 authz/src/main/webapp/favicon.ico create mode 100644 authz/src/main/webapp/loading.jsp rename authz/src/main/{resources/static => webapp/resources}/OS_API-0.2.html (100%) rename authz/src/main/{resources/static => webapp/resources}/OS_API-0.3.html (100%) rename authz/src/main/{resources/static => webapp/resources}/angular.min.js (100%) rename authz/src/main/{resources/static => webapp/resources}/bootstrap.min.css (100%) rename authz/src/main/{resources/static => webapp/resources}/oauth_test.html (100%) rename authz/src/main/{resources/static => webapp/resources}/readme.txt (100%) diff --git a/authz/pom.xml b/authz/pom.xml index e52c611..5bc1dfd 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -80,11 +80,6 @@ org.springframework.boot spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-thymeleaf - mysql @@ -96,6 +91,25 @@ HikariCP + + org.sitemesh + sitemesh + 3.0.1 + + + + org.apache.tomcat.embed + tomcat-embed-jasper + provided + + + javax.servlet + javax.servlet-api + + + javax.servlet + jstl + diff --git a/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java b/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java index 048273c..c9cad5b 100644 --- a/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java +++ b/authz/src/main/java/com/monkeyk/os/web/controller/ShiroController.java @@ -36,7 +36,7 @@ public class ShiroController { } - /* + /** * Go login page */ @RequestMapping(value = "login", method = RequestMethod.GET) diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 369db41..28ade2a 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -23,5 +23,7 @@ spring.datasource.hikari.maximum-pool-size=20 # # # MVC +spring.mvc.view.prefix=/WEB-INF/jsp/ +spring.mvc.view.suffix=.jsp spring.mvc.ignore-default-model-on-redirect=false # diff --git a/authz/src/main/webapp/WEB-INF/decorators.xml.old b/authz/src/main/webapp/WEB-INF/decorators.xml.old new file mode 100644 index 0000000..1fa1456 --- /dev/null +++ b/authz/src/main/webapp/WEB-INF/decorators.xml.old @@ -0,0 +1,18 @@ + + + + + + + + /* + + + + + /resources/** + + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/comm-header.jsp b/authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp similarity index 100% rename from authz/src/main/resources/templates/comm-header.jsp rename to authz/src/main/webapp/WEB-INF/jsp/comm-header.jsp diff --git a/authz/src/main/resources/templates/decorators/main.jsp b/authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp similarity index 91% rename from authz/src/main/resources/templates/decorators/main.jsp rename to authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp index e619c30..085a126 100644 --- a/authz/src/main/resources/templates/decorators/main.jsp +++ b/authz/src/main/webapp/WEB-INF/jsp/decorators/main.jsp @@ -34,7 +34,7 @@

- © oauth2-shiro + © 2015-2021 oauth2-shiro
diff --git a/authz/src/main/resources/templates/index.jsp b/authz/src/main/webapp/WEB-INF/jsp/index.jsp similarity index 100% rename from authz/src/main/resources/templates/index.jsp rename to authz/src/main/webapp/WEB-INF/jsp/index.jsp diff --git a/authz/src/main/resources/templates/login.jsp b/authz/src/main/webapp/WEB-INF/jsp/login.jsp similarity index 100% rename from authz/src/main/resources/templates/login.jsp rename to authz/src/main/webapp/WEB-INF/jsp/login.jsp diff --git a/authz/src/main/resources/templates/oauth/client_details.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp similarity index 100% rename from authz/src/main/resources/templates/oauth/client_details.jsp rename to authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp diff --git a/authz/src/main/resources/templates/oauth/client_details_plus.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp similarity index 100% rename from authz/src/main/resources/templates/oauth/client_details_plus.jsp rename to authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp diff --git a/authz/src/main/resources/templates/oauth/oauth_approval.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp similarity index 100% rename from authz/src/main/resources/templates/oauth/oauth_approval.jsp rename to authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp diff --git a/authz/src/main/resources/templates/oauth/oauth_login.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp similarity index 100% rename from authz/src/main/resources/templates/oauth/oauth_login.jsp rename to authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp diff --git a/authz/src/main/resources/templates/oauth/test_client.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp similarity index 100% rename from authz/src/main/resources/templates/oauth/test_client.jsp rename to authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp diff --git a/authz/src/main/resources/templates/unauthorized.jsp b/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp similarity index 100% rename from authz/src/main/resources/templates/unauthorized.jsp rename to authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp diff --git a/authz/src/main/resources/templates/users/user_plus.jsp b/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp similarity index 100% rename from authz/src/main/resources/templates/users/user_plus.jsp rename to authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp diff --git a/authz/src/main/resources/templates/users/users_overview.jsp b/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp similarity index 100% rename from authz/src/main/resources/templates/users/users_overview.jsp rename to authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp diff --git a/authz/src/main/webapp/WEB-INF/log4j.xml.old b/authz/src/main/webapp/WEB-INF/log4j.xml.old new file mode 100644 index 0000000..8f19edd --- /dev/null +++ b/authz/src/main/webapp/WEB-INF/log4j.xml.old @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old b/authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old new file mode 100644 index 0000000..ce7dffb --- /dev/null +++ b/authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/web.xml.old b/authz/src/main/webapp/WEB-INF/web.xml.old new file mode 100644 index 0000000..3c11874 --- /dev/null +++ b/authz/src/main/webapp/WEB-INF/web.xml.old @@ -0,0 +1,169 @@ + + + + oauth2-shiro + + + + + + + webAppRootKey + oauth2-shiro + + + + + encodingFilter + com.monkeyk.os.web.context.MkkCharacterEncodingFilter + + encoding + UTF-8 + + + forceEncoding + true + + + + encodingFilter + /* + + + + + gzipFilter + + net.sf.ehcache.constructs.web.filter.GzipFilter + + + + gzipFilter + *.css + + + gzipFilter + *.png + + + gzipFilter + *.gif + + + gzipFilter + *.html + + + gzipFilter + *.js + + + + + + shiroFilter + org.springframework.web.filter.DelegatingFilterProxy + + targetFilterLifecycle + true + + + + + shiroFilter + /* + + + + + + sitemesh + com.opensymphony.sitemesh.webapp.SiteMeshFilter + + + sitemesh + /* + + + + ico + image/vnd.microsoft.icon + + + + + contextConfigLocation + classpath:spring/*.xml + + + + + log4jConfigLocation + /WEB-INF/log4j.xml + + + org.springframework.web.util.Log4jConfigListener + + + + + + com.monkeyk.os.web.context.BeanContextLoaderListener + + + + + mkk + org.springframework.web.servlet.DispatcherServlet + 2 + + + mkk + / + + + + + + + + + + + 30 + + + + + loading.jsp + + + + \ No newline at end of file diff --git a/authz/src/main/webapp/favicon.ico b/authz/src/main/webapp/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..26d798de88313b1e60f65a967249b36022078d07 GIT binary patch literal 1150 zcmds#u}VWh5Jhi5uuM=91d$XXVwJ+eLL`+%K0(AHl^+l+LqIWQB32fDfuCXRH;AQ( zRj?3|cy6)-V<2Ly^Ef+qW_I?yx4Tk>r&beVU8f7us-$foMP{1EN!0$Sb@?w-hxMWz zufS%CkrUfzbiF+dHO8AJoEPo51BWR_Y8OWE3Oitne$RbQ@e{h0;w|(GyuluXMgJ`c zYxjAc^-b~CkMIFrGn;G1>)M||zv#$0{|P5BYtbKTEHPmN{H|R4O~3yk><*Fpy=Omt zzvnih8Sit-{Y~O`n74oT)Mo90M6BW3af$Q!`7W4zCWI3^V2pZ6>8Rx5{zU%I`7t|_ Zx#G4Buch9ibWxQq4SKK)Yv1R(?+bddY={5= literal 0 HcmV?d00001 diff --git a/authz/src/main/webapp/loading.jsp b/authz/src/main/webapp/loading.jsp new file mode 100644 index 0000000..817b6df --- /dev/null +++ b/authz/src/main/webapp/loading.jsp @@ -0,0 +1,9 @@ +<%-- + * + * @author Shengzhao Li +--%> + +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<% + request.getRequestDispatcher("index").forward(request, response); +%> \ No newline at end of file diff --git a/authz/src/main/resources/static/OS_API-0.2.html b/authz/src/main/webapp/resources/OS_API-0.2.html similarity index 100% rename from authz/src/main/resources/static/OS_API-0.2.html rename to authz/src/main/webapp/resources/OS_API-0.2.html diff --git a/authz/src/main/resources/static/OS_API-0.3.html b/authz/src/main/webapp/resources/OS_API-0.3.html similarity index 100% rename from authz/src/main/resources/static/OS_API-0.3.html rename to authz/src/main/webapp/resources/OS_API-0.3.html diff --git a/authz/src/main/resources/static/angular.min.js b/authz/src/main/webapp/resources/angular.min.js similarity index 100% rename from authz/src/main/resources/static/angular.min.js rename to authz/src/main/webapp/resources/angular.min.js diff --git a/authz/src/main/resources/static/bootstrap.min.css b/authz/src/main/webapp/resources/bootstrap.min.css similarity index 100% rename from authz/src/main/resources/static/bootstrap.min.css rename to authz/src/main/webapp/resources/bootstrap.min.css diff --git a/authz/src/main/resources/static/oauth_test.html b/authz/src/main/webapp/resources/oauth_test.html similarity index 100% rename from authz/src/main/resources/static/oauth_test.html rename to authz/src/main/webapp/resources/oauth_test.html diff --git a/authz/src/main/resources/static/readme.txt b/authz/src/main/webapp/resources/readme.txt similarity index 100% rename from authz/src/main/resources/static/readme.txt rename to authz/src/main/webapp/resources/readme.txt -- Gitee From 77b3227d6abe7575925f8f7cc22ef99c17bd668c Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 18 Sep 2023 18:51:28 +0800 Subject: [PATCH 15/78] Jsp transfer to html (thymeleaf) --- authz/pom.xml | 29 +- .../src/main/resources/application.properties | 5 +- .../src/main/resources/logging.properties.old | 14 - .../src/main/resources/static/OS_API-0.2.html | 555 +++++++++++++++ .../src/main/resources/static/OS_API-0.3.html | 636 ++++++++++++++++++ .../src/main/resources/static/angular.min.js | 178 +++++ .../main/resources/static/bootstrap.min.css | 5 + .../src/main/resources/static/oauth_test.html | 251 +++++++ authz/src/main/resources/templates/login.html | 118 ++++ resources/pom.xml | 6 - 10 files changed, 1749 insertions(+), 48 deletions(-) delete mode 100644 authz/src/main/resources/logging.properties.old create mode 100644 authz/src/main/resources/static/OS_API-0.2.html create mode 100644 authz/src/main/resources/static/OS_API-0.3.html create mode 100644 authz/src/main/resources/static/angular.min.js create mode 100644 authz/src/main/resources/static/bootstrap.min.css create mode 100644 authz/src/main/resources/static/oauth_test.html create mode 100644 authz/src/main/resources/templates/login.html diff --git a/authz/pom.xml b/authz/pom.xml index 5bc1dfd..80c4d7a 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -80,6 +80,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-thymeleaf + mysql @@ -91,37 +95,12 @@ HikariCP - - org.sitemesh - sitemesh - 3.0.1 - - - - org.apache.tomcat.embed - tomcat-embed-jasper - provided - - - javax.servlet - javax.servlet-api - - - javax.servlet - jstl - org.springframework.boot spring-boot-starter-test test - - - org.junit.vintage - junit-vintage-engine - - diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 28ade2a..5fff5b1 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -23,7 +23,6 @@ spring.datasource.hikari.maximum-pool-size=20 # # # MVC -spring.mvc.view.prefix=/WEB-INF/jsp/ -spring.mvc.view.suffix=.jsp -spring.mvc.ignore-default-model-on-redirect=false +spring.thymeleaf.encoding=UTF-8 +spring.thymeleaf.cache=false # diff --git a/authz/src/main/resources/logging.properties.old b/authz/src/main/resources/logging.properties.old deleted file mode 100644 index 1d66b3e..0000000 --- a/authz/src/main/resources/logging.properties.old +++ /dev/null @@ -1,14 +0,0 @@ -handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler - -############################################################ -# Handler specific properties. -# Describes specific configuration info for Handlers. -# The configuration for Tomcat server -############################################################ - -org.apache.juli.FileHandler.level = FINE -org.apache.juli.FileHandler.directory = ${catalina.base}/logs -org.apache.juli.FileHandler.prefix = error-debug. - -java.util.logging.ConsoleHandler.level = FINE -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter diff --git a/authz/src/main/resources/static/OS_API-0.2.html b/authz/src/main/resources/static/OS_API-0.2.html new file mode 100644 index 0000000..bbd9fbd --- /dev/null +++ b/authz/src/main/resources/static/OS_API-0.2.html @@ -0,0 +1,555 @@ + + + + + + + + oauth2-shiro API + + + + + +
+ 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
+ +
+ +
+ +
+

[authz]

+ +

获取access_token (grant_type=password) + public +

+ +

使用grant_type=password方式来获取access_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typepassword固定值
    scope{scope}read or write
    username{username}用户名
    password{password}用户密码
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} + +

      +
    • +
    • +

      + 异常 [400]
      + + {"error":"invalid_grant","error_description":"Bad credentials"} + +

      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=authorization_code) + public +

+ +

使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeauthorization_code固定值
    code{code}
    redirect_uri{redirect_uri}
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} + +

      +
    • +
    • +

      + 异常 [400]
      + + {"error":"invalid_grant","error_description":"Invalid code + '26964e42c667b5d42f89a1255766630a'"} + +

      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=client_credentials) + public +

+ +

使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeclient_credentials固定值
    scope{scope}read or write
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} + +

      +
    • +
    • +

      + 异常 [401]
      + + {"error":"invalid_client","error_description":"Invalid client_id + 'OMN4XjXmJidyzhUGWVrdk'"} + +

      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

刷新access_token (grant_type=refresh_token) + public +

+ +

用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typerefresh_token固定值
    refresh_token{refresh_token}
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} + +

      +
    • +
    • +

      + 异常 [400]
      + + {"error":"invalid_grant","error_description":"Invalid refresh_token: + 8e91a56f53857688a3ffd8c7cfd311cfss"} + +

      +
    • +
    +
  • +
+
+ +
+ +
+

[resources]

+ +

获取当前系统时间(resource-id: mobile-resource)

+ +

获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

+ +
    +
  • +

    + 请求URI: /mobile/system_time GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +

    + http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"time":1465560577614} + +

      +
    • +
    • +

      + 异常 [401]
      + + {"error":"invalid_token","error_description":"Invalid access_token: + 95c3afd44c5d87301dc3034b20b3fc75s"} + +

      +
    • +
    +
  • +
+
+ +
+

[resources]

+ +

获取当前用户信息 (resource-id: os-resource; Role: User)

+ +

使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

+ +
    +
  • +

    + 请求URI: /rs/username GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +

    + http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} + +

      +
    • +
    • +

      + 异常 [401]
      + + {"error":"invalid_token","error_description":"Invalid client by token: + 95c3afd44c5d87301dc3034b20b3fc75"} + +

      +
    • +
    +
  • +
+
+ +
+
+ + +
+
+
+
+ © oauth2-shiro +
+
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/static/OS_API-0.3.html b/authz/src/main/resources/static/OS_API-0.3.html new file mode 100644 index 0000000..843356a --- /dev/null +++ b/authz/src/main/resources/static/OS_API-0.3.html @@ -0,0 +1,636 @@ + + + + + + + + oauth2-shiro API + + + + + +
+ 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
+ +
+ +
+ +
+

[authz]

+ +

获取access_token (grant_type=password) + public +

+ +

使用grant_type=password方式来获取access_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typepassword固定值
    scope{scope}read or write
    username{username}用户名
    password{password}用户密码
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} + +

      +
    • +
    • +

      + 异常 [400]
      + + {"error":"invalid_grant","error_description":"Bad credentials"} + +

      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=authorization_code) + public +

+ +

使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeauthorization_code固定值
    code{code}
    redirect_uri{redirect_uri}
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} + +

      +
    • +
    • +

      + 异常 [400]
      + + {"error":"invalid_grant","error_description":"Invalid code + '26964e42c667b5d42f89a1255766630a'"} + +

      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=token) + public +

+ +

使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

+ +
    +
  • +

    + 请求URI: /oauth/token GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    grant_typetoken固定值
    scope{scope}read or write
    redirect_uri{redirect_uri}
    + 请求示例: +

    + http://localhost:8080/authz/oauth/authorize?client_id=DYBeQ5lWFKyhY0TDSxxU&response_type=token&scope=read&redirect_uri=http://www.example.com +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + http://www.example.com#token_type=Bearer&expires_in=43199&access_token=fa3bb346a04e20431161771c0003c466 + +

      +
    • +
    +

    通过 redirect_uri的 URL hash 传递access_token信息

    +
  • +
+
+ +
+

[authz]

+ +

获取access_token (grant_type=client_credentials) + public +

+ +

使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typeclient_credentials固定值
    scope{scope}read or write
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} + +

      +
    • +
    • +

      + 异常 [401]
      + + {"error":"invalid_client","error_description":"Invalid client_id + 'OMN4XjXmJidyzhUGWVrdk'"} + +

      +
    • +
    +
  • +
+
+ +
+

[authz]

+ +

刷新access_token (grant_type=refresh_token) + public +

+ +

用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

+ +
    +
  • +

    + 请求URI: /oauth/token POST +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    client_id{client_id}
    client_secret{client_secret}
    grant_typerefresh_token固定值
    refresh_token{refresh_token}
    + 请求示例: +

    + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} + +

      +
    • +
    • +

      + 异常 [400]
      + + {"error":"invalid_grant","error_description":"Invalid refresh_token: + 8e91a56f53857688a3ffd8c7cfd311cfss"} + +

      +
    • +
    +
  • +
+
+ +
+ +
+

[resources]

+ +

获取当前系统时间(resource-id: mobile-resource)

+ +

获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

+ +
    +
  • +

    + 请求URI: /mobile/system_time GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +

    + http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"time":1465560577614} + +

      +
    • +
    • +

      + 异常 [401]
      + + {"error":"invalid_token","error_description":"Invalid access_token: + 95c3afd44c5d87301dc3034b20b3fc75s"} + +

      +
    • +
    +
  • +
+
+ +
+

[resources]

+ +

获取当前用户信息 (resource-id: os-resource; Role: User)

+ +

使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

+ +
    +
  • +

    + 请求URI: /rs/username GET +

    + +
    + 请求参数说明: + + + + + + + + + + + + + + +
    参数名参数值必须?备注
    + 请求示例: +

    + http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

    + +
    +
    + + 响应 + +
      +
    • +

      + 正常 [200]
      + + {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} + +

      +
    • +
    • +

      + 异常 [401]
      + + {"error":"invalid_token","error_description":"Invalid client by token: + 95c3afd44c5d87301dc3034b20b3fc75"} + +

      +
    • +
    +
  • +
+
+ +
+
+ + +
+
+
+
+ © oauth2-shiro +
+
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/static/angular.min.js b/authz/src/main/resources/static/angular.min.js new file mode 100644 index 0000000..ac033dc --- /dev/null +++ b/authz/src/main/resources/static/angular.min.js @@ -0,0 +1,178 @@ +/* + AngularJS v1.1.5 + (c) 2010-2012 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(M,T,p){'use strict';function lc(){var b=M.angular;M.angular=mc;return b}function Xa(b){return!b||typeof b.length!=="number"?!1:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"?!0:b instanceof R||ga&&b instanceof ga||Ea.call(b)!=="[object Object]"||typeof b.callee==="function"}function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==n)b.forEach(a,c);else if(Xa(b))for(d= +0;d=0&&b.splice(c,1);return a}function V(b,a){if(sa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(F(b))for(var c=a.length=0;c2?ka.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ka.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function qc(b,a){var c=a;/^\$+/.test(b)?c=p:sa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&& +a.$watch&&(c="$SCOPE");return c}function ha(b,a){return JSON.stringify(b,qc,a?" ":null)}function ub(b){return E(b)?JSON.parse(b):b}function ua(b){b&&b.length!==0?(b=I(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function va(b){b=w(b).clone();try{b.html("")}catch(a){}var c=w("
").append(b).html();try{return b[0].nodeType===3?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+I(b)})}catch(d){return I(c)}}function vb(b){var a={},c,d;n((b|| +"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=B(c[1])?decodeURIComponent(c[1]):!0)});return a}function wb(b){var a=[];n(b,function(b,d){a.push(wa(d,!0)+(b===!0?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function ab(b){return wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function rc(b, +a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;n(i,function(a){i[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(n(b.querySelectorAll("."+a),c),n(b.querySelectorAll("."+a+"\\:"),c),n(b.querySelectorAll("["+a+"]"),c))});n(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):n(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])} +function xb(b,a){var c=function(){b=w(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=yb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animator",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)});e.enabled(!0)}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(M&&!d.test(M.name))return c();M.name=M.name.replace(d,"");Ha.resumeBootstrap=function(b){n(b,function(b){a.push(b)});c()}}function bb(b,a){a=a||"_";return b.replace(sc, +function(b,d){return(d?a:"")+b.toLowerCase()})}function cb(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function xa(b,a,c){c&&F(b)&&(b=b[b.length-1]);cb(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function tc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c, +d,e){return function(){b[e||"push"]([c,d,arguments]);return m}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector","invoke"),m={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider", +"directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return m})}})}function Ia(b){return b.replace(uc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(vc,"Moz$1")}function db(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,j,m,k;b.length;){i=b.shift();f=0;for(h=i.length;f 
"+b;a.removeChild(a.firstChild);eb(this,a.childNodes);this.remove()}else eb(this,b)}function fb(b){return b.cloneNode(!0)}function ya(b){zb(b);for(var a=0,b=b.childNodes||[];a-1}function Cb(b,a){a&&n(a.split(" "),function(a){b.className=U((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+U(a)+" "," "))})}function Db(b,a){a&&n(a.split(" "),function(a){if(!La(b,a))b.className=U(b.className+" "+U(a))})}function eb(b,a){if(a)for(var a=!a.nodeName&&B(a.length)&&!sa(a)?a:[a],c=0;c 4096 bytes)!")}else{if(h.cookie!==D){D=h.cookie;d=D.split("; ");G={};for(f=0;f0&&(a=unescape(e.substring(0,j)),G[a]===p&&(G[a]=unescape(e.substring(j+1))))}return G}};f.defer=function(a,b){var c;o++;c=k(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};f.defer.cancel=function(a){return u[a]?(delete u[a],l(a),e(q),!0):!1}}function Ec(){this.$get= +["$window","$log","$sniffer","$document",function(b,a,c,d){return new Dc(b,d,a,c)}]}function Fc(){this.$get=function(){function b(b,d){function e(a){if(a!=k){if(l){if(l==a)l=a.n}else l=a;g(a.n,a.p);g(a,k);k=a;k.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var i=0,f=t({},d,{id:b}),h={},j=d&&d.capacity||Number.MAX_VALUE,m={},k=null,l=null;return a[b]={put:function(a,b){var c=m[a]||(m[a]={key:a});e(c);if(!C(b))return a in h||i++,h[a]=b,i>j&&this.remove(l.key), +b},get:function(a){var b=m[a];if(b)return e(b),h[a]},remove:function(a){var b=m[a];if(b){if(b==k)k=b.p;if(b==l)l=b.n;g(b.n,b.p);delete m[a];delete h[a];i--}},removeAll:function(){h={};i=0;m={};k=l=null},destroy:function(){m=f=h=null;delete a[b]},info:function(){return t({},f,{size:i})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function Gc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Jb(b){var a= +{},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",i=/^\s*(https?|ftp|mailto|file):/;this.directive=function h(d,e){E(d)?(cb(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:S(g)};else if(!g.compile&&g.link)g.compile=S(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require= +g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,rb(h));return this};this.urlSanitizationWhitelist=function(a){return B(a)?(i=a,this):i};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,j,m,k,l,u,o,z,r){function y(a,b,c){a instanceof w||(a=w(a));n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=w(b).wrap("").parent()[0])}); +var d=W(a,b,a,c);return function(b,c){cb(b,"scope");for(var e=c?Ba.clone.call(a):a,j=0,g=e.length;js.priority)break;if(t=s.scope)O("isolated scope",K,s,J),L(t)&&(x(J,"ng-isolate-scope"),K=s),x(J,"ng-scope"),r=r||s;A=s.name;if(t=s.controller)q=q||{},O("'"+A+"' controller",q[A],s,J),q[A]=s;if(t=s.transclude)O("transclusion",G,s,J),G=s,l=s.priority,t=="element"?(Y=w(b),J=c.$$element=w(T.createComment(" "+A+": "+c[A]+" ")),b=J[0],ja(e,w(Y[0]),b),P=y(Y,d,l)):(Y=w(fb(b)).contents(), +J.html(""),P=y(Y,d));if(s.template)if(O("template",W,s,J),W=s,t=H(s.template)?s.template(J,c):s.template,t=Lb(t),s.replace){Y=w("
"+U(t)+"
").contents();b=Y[0];if(Y.length!=1||b.nodeType!==1)throw Error(g+t);ja(e,J,b);A={$attr:{}};a=a.concat(v(b,a.splice(B+1,a.length-(B+1)),A));D(c,A);C=a.length}else J.html(t);if(s.templateUrl)O("template",W,s,J),W=s,k=$(a.splice(B,a.length-B),k,J,c,e,s.replace,P),C=a.length;else if(s.compile)try{na=s.compile(J,c,P),H(na)?h(null,na):na&&h(na.pre,na.post)}catch(I){m(I, +va(J))}if(s.terminal)k.terminal=!0,l=Math.max(l,s.priority)}k.scope=r&&r.scope;k.transclude=G&&P;return k}function G(d,e,j,g){var l=!1;if(a.hasOwnProperty(e))for(var k,e=b.get(e+c),i=0,o=e.length;ik.priority)&&k.restrict.indexOf(j)!=-1)d.push(k),l=!0}catch(u){m(u)}return l}function D(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,j){j=="class"?(x(e,b),a["class"]= +(a["class"]?a["class"]+" ":"")+b):j=="style"?e.attr("style",e.attr("style")+";"+b):j.charAt(0)!="$"&&!a.hasOwnProperty(j)&&(a[j]=b,d[j]=c[j])})}function $(a,b,c,d,e,j,h){var i=[],o,m,u=c[0],z=a.shift(),r=t({},z,{controller:null,templateUrl:null,transclude:null,scope:null}),z=H(z.templateUrl)?z.templateUrl(c,d):z.templateUrl;c.html("");k.get(z,{cache:l}).success(function(l){var k,z,l=Lb(l);if(j){z=w("
"+U(l)+"
").contents();k=z[0];if(z.length!=1||k.nodeType!==1)throw Error(g+l);l={$attr:{}}; +ja(e,c,k);v(k,a,l);D(d,l)}else k=u,c.html(l);a.unshift(r);o=A(a,k,d,h);for(m=W(c[0].childNodes,h);i.length;){var ea=i.shift(),l=i.shift();z=i.shift();var x=i.shift(),y=k;l!==u&&(y=fb(k),ja(z,w(l),y));o(function(){b(m,ea,y,e,x)},ea,y,e,x)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,j){i?(i.push(c),i.push(d),i.push(e),i.push(j)):o(function(){b(m,c,d,e,j)},c,d,e,j)}}function K(a,b){return b.priority-a.priority}function O(a,b,c,d){if(b)throw Error("Multiple directives ["+ +b.name+", "+c.name+"] asking for "+a+" on: "+va(d));}function P(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:S(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);x(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function s(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:S(function(a,b,c){b=c.$$observers||(c.$$observers={});if(e=j(c[d],!0))c[d]=e(a),(b[d]||(b[d]=[])).$$inter=!0,(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d, +a)})})})}function ja(a,b,c){var d=b[0],e=d.parentNode,j,g;if(a){j=0;for(g=a.length;j0){var e=O[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b, +c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),O.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function j(a,b){return t(function(c,d){return a(c,d,b)},{constant:b.constant})}function m(a,b,c){return t(function(d,e){return a(d,e)?b(d,e):c(d,e)},{constant:a.constant&&b.constant&&c.constant})}function k(a,b,c){return t(function(d,e){return b(d,e,a,c)},{constant:a.constant&&c.constant})}function l(){for(var a=[];;)if(O.length>0&&!i("}",")",";","]")&&a.push(w()), +!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,x());return a}function n(){for(var a=v(),b;b=f("*","/","%");)a=k(a,b.fn,v());return a}function v(){var a;return f("+")?A():(a=f("-"))?k($,a.fn,v()):(a=f("!"))?j(a.fn,v()):A()}function A(){var a;if(f("("))a=w(),h(")");else if(f("["))a=G();else if(f("{"))a=D();else{var b=f();(a= +b.fn)||e("not a primary expression",b);if(b.json)a.constant=a.literal=!0}for(var c;b=f("(","[",".");)b.text==="("?(a=s(a,c),c=null):b.text==="["?(c=a,a=ma(a)):b.text==="."?(c=a,a=ja(a)):e("IMPOSSIBLE");return a}function G(){var a=[],b=!0;if(g().text!="]"){do{var c=P();a.push(c);c.constant||(b=!1)}while(f(","))}h("]");return t(function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function ib(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;ia)for(b in g++,e)e.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(x--,delete e[b])}else e!==f&&(e=f,g++);return g}, +function(){b(f,e,c)})},$digest:function(){var a,d,e,i,u=this.$$asyncQueue,o,z,r=b,n,x=[],p,v;g("$digest");do{z=!1;for(n=this;u.length;)try{n.$eval(u.shift())}catch(A){c(A)}do{if(i=n.$$watchers)for(o=i.length;o--;)try{if(a=i[o],(d=a.get(n))!==(e=a.last)&&!(a.eq?ia(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))z=!0,a.last=a.eq?V(d):d,a.fn(d,e===f?d:e,n),r<5&&(p=4-r,x[p]||(x[p]=[]),v=H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,v+="; newVal: "+ha(d)+"; oldVal: "+ha(e),x[p].push(v))}catch(G){c(G)}if(!(i= +n.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(i=n.$$nextSibling);)n=n.$parent}while(n=i);if(z&&!r--)throw h.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+ha(x));}while(z||u.length);h.$$phase=null},$destroy:function(){if(!(h==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling; +if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{h.$$phase=null;try{h.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a]; +c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Ga(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,i={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){i.defaultPrevented=!0},defaultPrevented:!1},h=[i].concat(ka.call(arguments,1)),n,x;do{e=f.$$listeners[a]||d;i.currentScope=f;n=0;for(x=e.length;n7),hasEvent:function(a){if(a=="input"&&Z==9)return!1;if(C(c[a])){var b= +e.createElement("div");c[a]="on"+a in b}return c[a]},csp:e.securityPolicy?e.securityPolicy.isActive:!1,vendorPrefix:g,transitions:h,animations:j}}]}function Zc(){this.$get=S(M)}function Wb(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b){e=b.indexOf(":");c=I(U(b.substr(0,e)));d=U(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function $c(b,a){var c=ad.exec(b);if(c==null)return!0;var d={protocol:c[2],host:c[4],port:N(c[6])||Oa[c[2]]||null,relativeProtocol:c[2]===p||c[2]===""}, +c=jb.exec(a),c={protocol:c[1],host:c[3],port:N(c[5])||Oa[c[1]]||null};return(d.protocol==c.protocol||d.relativeProtocol)&&d.host==c.host&&(d.port==c.port||d.relativeProtocol&&c.port==Oa[c.protocol])}function Xb(b){var a=L(b)?b:p;return function(c){a||(a=Wb(b));return c?a[I(c)]||null:a}}function Yb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function bd(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults= +{transformResponse:[function(d){E(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ub(d,!0)));return d}],transformRequest:[function(a){return L(a)&&Ea.apply(a)!=="[object File]"?ha(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],i=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,k,l){function u(a){function c(a){var b= +t({},a,{data:Yb(a.data,a.headers,d.transformResponse)});return 200<=a.status&&a.status<300?b:k.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f={};t(d,a);d.headers=f;d.method=oa(d.method);t(f,e.headers.common,e.headers[I(d.method)],a.headers);(a=$c(d.url,b.url())?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:p)&&(f[d.xsrfHeaderName||e.xsrfHeaderName]=a);var g=[function(a){var b=Yb(a.data,Xb(f),a.transformRequest);C(a.data)&&delete f["Content-Type"];if(C(a.withCredentials)&& +!C(e.withCredentials))a.withCredentials=e.withCredentials;return o(a,b,f).then(c,c)},p],j=k.when(d);for(n(y,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;)var a=g.shift(),i=g.shift(),j=j.then(a,i);j.success=function(a){j.then(function(b){a(b.data,b.status,b.headers,d)});return j};j.error=function(a){j.then(null,function(b){a(b.data,b.status,b.headers,d)});return j};return j}function o(b,c,g){function j(a, +b,c){n&&(200<=a&&a<300?n.put(s,[a,b,Wb(c)]):n.remove(s));i(b,a,c);d.$$phase||d.$apply()}function i(a,c,d){c=Math.max(c,0);(200<=c&&c<300?l.resolve:l.reject)({data:a,status:c,headers:Xb(d),config:b})}function h(){var a=Ga(u.pendingRequests,b);a!==-1&&u.pendingRequests.splice(a,1)}var l=k.defer(),o=l.promise,n,p,s=z(b.url,b.params);u.pendingRequests.push(b);o.then(h,h);if((b.cache||e.cache)&&b.cache!==!1&&b.method=="GET")n=L(b.cache)?b.cache:L(e.cache)?e.cache:r;if(n)if(p=n.get(s))if(p.then)return p.then(h, +h),p;else F(p)?i(p[1],p[0],V(p[2])):i(p,200,{});else n.put(s,o);p||a(b.method,s,c,j,g,b.timeout,b.withCredentials,b.responseType);return o}function z(a,b){if(!b)return a;var c=[];nc(b,function(a,b){a==null||a==p||(F(a)||(a=[a]),n(a,function(a){L(a)&&(a=ha(a));c.push(wa(b)+"="+wa(a))}))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var r=c("$http"),y=[];n(g,function(a){y.unshift(E(a)?l.get(a):l.invoke(a))});n(i,function(a,b){var c=E(a)?l.get(a):l.invoke(a);y.splice(b,0,{response:function(a){return c(k.when(a))}, +responseError:function(a){return c(k.reject(a))}})});u.pendingRequests=[];(function(a){n(arguments,function(a){u[a]=function(b,c){return u(t(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){n(arguments,function(a){u[a]=function(b,c,d){return u(t(d||{},{method:a,url:b,data:c}))}})})("post","put");u.defaults=e;return u}]}function cd(){this.$get=["$browser","$window","$document",function(b,a,c){return dd(b,ed,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]} +function dd(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c);return d}return function(e,h,j,m,k,l,u,o){function z(){p=-1;t&&t();v&&v.abort()}function r(a,d,e,f){var j=(h.match(jb)||["",g])[1];A&&c.cancel(A);t=v=null;d=j=="file"?e?200:404:d;a(d==1223?204:d,e,f);b.$$completeOutstandingRequest(q)} +var p;b.$$incOutstandingRequestCount();h=h||b.url();if(I(e)=="jsonp"){var x="_"+(d.counter++).toString(36);d[x]=function(a){d[x].data=a};var t=i(h.replace("JSON_CALLBACK","angular.callbacks."+x),function(){d[x].data?r(m,200,d[x].data):r(m,p||-2);delete d[x]})}else{var v=new a;v.open(e,h,!0);n(k,function(a,b){a&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v.readyState==4){var a=v.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified", +"Pragma"];a||(a="",n(b,function(b){var c=v.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));r(m,p||v.status,v.responseType?v.response:v.responseText,a)}};if(u)v.withCredentials=!0;if(o)v.responseType=o;v.send(j||"")}if(l>0)var A=c(z,l);else l&&l.then&&l.then(z)}}function fd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4", +posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y", +mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function gd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var j=c.defer(),m=j.promise,k=B(h)&&!h,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}k||b.$apply()},f),h=function(){delete g[m.$$timeoutId]};m.$$timeoutId=f;g[f]=j;m.then(h,h);return m}var g={};e.cancel=function(b){return b&&b.$$timeoutId in +g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Zb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",$b);a("date",ac);a("filter",hd);a("json",id);a("limitTo",jd);a("lowercase",kd);a("number",bc);a("orderBy",cc);a("uppercase",ld)}function hd(){return function(b,a,c){if(!F(b))return b;var d=[];d.check=function(a){for(var b=0;b-1}}var e=function(a,b){if(typeof b=="string"&&b.charAt(0)==="!")return!e(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if(d.charAt(0)!=="$"&&e(a[d],b))return!0}return!1;case "array":for(d= +0;de+1?i="0":(f=i,j=!0)}if(!j){i=(i.split(ec)[1]||"").length;C(e)&&(e=Math.min(Math.max(a.minFrac,i), +a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(ec),i=b[0],b=b[1]||"",j=0,m=a.lgSize,k=a.gSize;if(i.length>=m+k)for(var j=i.length-m,l=0;l0||e>-c)e+=c;e===0&&c==-12&&(e=12);return nb(e,a,d)}}function Qa(b,a){return function(c,d){var e=c["get"+b](),g=oa(a?"SHORT"+b:b);return d[g][e]}}function ac(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0,f=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=N(b[9]+b[10]),i=N(b[9]+b[11]));f.call(a,N(b[1]),N(b[2])-1,N(b[3]));g=N(b[4]||0)-g;i=N(b[5]||0)-i;f= +N(b[6]||0);b=Math.round(parseFloat("0."+(b[7]||0))*1E3);h.call(a,g,i,f,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",i=[],f,h,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;E(c)&&(c=md.test(c)?N(c):a(c));Ya(c)&&(c=new Date(c));if(!ra(c))return c;for(;e;)(h=nd.exec(e))?(i=i.concat(ka.call(h,1)),e=i.pop()):(i.push(e),e=null);n(i,function(a){f=od[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g, +"").replace(/''/g,"'")});return g}}function id(){return function(b){return ha(b,!0)}}function jd(){return function(b,a){if(!F(b)&&!E(b))return b;a=N(a);if(E(b))return a?a>=0?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}}function ob(b,a){b="ngClass"+b;return aa(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)h&&!ia(b,h)&&i(h),f(b);h=V(b)}function i(a){L(a)&&!F(a)&&(a=Za(a,function(a,b){if(a)return b}));d.removeClass(F(a)?a.join(" "):a)}function f(a){L(a)&&!F(a)&&(a=Za(a, +function(a,b){if(a)return b}));a&&d.addClass(F(a)?a.join(" "):a)}var h=p;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var h=d&1;h!==g&1&&(h===a?f(c.$eval(e[b])):i(c.$eval(e[b])))})})}var I=function(b){return E(b)?b.toLowerCase():b},oa=function(b){return E(b)?b.toUpperCase():b},Z=N((/msie (\d+)/.exec(I(navigator.userAgent))||[])[1]),w,ga,ka=[].slice,Wa=[].push,Ea=Object.prototype.toString,mc=M.angular,Ha=M.angular||(M.angular= +{}),Aa,hb,ba=["0","0","0"];q.$inject=[];qa.$inject=[];hb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?oa(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var sc=/[A-Z]/g,pd={full:"1.1.5",major:1,minor:1,dot:5,codeName:"triangle-squarification"},Ka=R.cache={},Ja=R.expando="ng-"+(new Date).getTime(),wc=1,gc=M.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},gb= +M.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},uc=/([\:\-\_]+(.))/g,vc=/^moz([A-Z])/,Ba=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;T.readyState==="complete"?setTimeout(a):(this.bind("DOMContentLoaded",a),R(M).bind("load",a))},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?w(this[b]):w(this[this.length+b])},length:0,push:Wa,sort:[].sort, +splice:[].splice},Na={};n("multiple,selected,checked,disabled,readOnly,required,open".split(","),function(b){Na[I(b)]=b});var Gb={};n("input,select,option,textarea,button,form,details".split(","),function(b){Gb[oa(b)]=!0});n({data:Bb,inheritedData:Ma,scope:function(b){return Ma(b,"$scope")},controller:Eb,injector:function(b){return Ma(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:La,css:function(b,a,c){a=Ia(a);if(B(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a], +d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=I(a);if(Na[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||q).specified?d:p;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:t(Z<9?function(b,a){if(b.nodeType==1){if(C(a))return b.innerText;b.innerText=a}else{if(C(a))return b.nodeValue; +b.nodeValue=a}}:function(b,a){if(C(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(C(a))return b.value;b.value=a},html:function(b,a){if(C(a))return b.innerHTML;for(var c=0,d=b.childNodes;c0||parseFloat(h[a+"Duration"])> +0)g="animation",i=a,j=Math.max(parseInt(h[g+"IterationCount"])||0,parseInt(h[i+"IterationCount"])||0,j);f=Math.max(x(h[g+"Delay"]),x(h[i+"Delay"]));g=Math.max(x(h[g+"Duration"]),x(h[i+"Duration"]));d=Math.max(f+j*g,d)}});e.setTimeout(v,d*1E3)}else v()}function v(){if(!v.run)v.run=!0,o(m,r,p),m.removeClass(w),m.removeClass(K),m.removeData(a)}var A=c.$eval(i.ngAnimate),w=A?L(A)?A[j]:A+"-"+j:"",D=d(w),A=D&&D.setup,$=D&&D.start,D=D&&D.cancel;if(w){var K=w+"-active";r||(r=p?p.parent():m.parent());if(!g.transitions&& +!A&&!$||(r.inheritedData(a)||q).running)k(m,r,p),o(m,r,p);else{var O=m.data(a)||{};O.running&&((D||q)(m),O.done());m.data(a,{running:!0,done:v});m.addClass(w);k(m,r,p);if(m.length==0)return v();var P=(A||q)(m);e.setTimeout(t,1)}}else k(m,r,p),o(m,r,p)}}function m(a,c,d){d?d.after(a):c.append(a)}var k={};k.enter=j("enter",m,q);k.leave=j("leave",q,function(a){a.remove()});k.move=j("move",function(a,c,d){m(a,c,d)},q);k.show=j("show",function(a){a.css("display","")},q);k.hide=j("hide",q,function(a){a.css("display", +"none")});k.animate=function(a,c){j(a,q,q)(c)};return k};i.enabled=function(a){if(arguments.length)c.running=!a;return!c.running};return i}]},Kb="Non-assignable model expression: ";Jb.$inject=["$provide"];var Ic=/^(x[\:\-_]|data[\:\-_])/i,jb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,Pb=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Oa={http:80,https:443,ftp:21};Rb.prototype=lb.prototype=Qb.prototype={$$replace:!1,absUrl:Pa("$$absUrl"),url:function(a,c){if(C(a))return this.$$url; +var d=Pb.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Pa("$$protocol"),host:Pa("$$host"),port:Pa("$$port"),path:Sb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(C(a))return this.$$search;B(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=E(a)?vb(a):a;this.$$compose();return this},hash:Sb("$$hash",qa),replace:function(){this.$$replace=!0;return this}};var Da={"null":function(){return null}, +"true":function(){return!0},"false":function(){return!1},undefined:q,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:p},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":q,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a, +c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Qc={n:"\n",f:"\u000c",r:"\r", +t:"\t",v:"\u000b","'":"'",'"':'"'},mb={},ad=/^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/,ed=M.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");};Zb.$inject=["$provide"];$b.$inject=["$locale"];bc.$inject=["$locale"];var ec=".",od={yyyy:Q("FullYear",4),yy:Q("FullYear", +2,0,!0),y:Q("FullYear",1),MMMM:Qa("Month"),MMM:Qa("Month",!0),MM:Q("Month",2,1),M:Q("Month",1,1),dd:Q("Date",2),d:Q("Date",1),HH:Q("Hours",2),H:Q("Hours",1),hh:Q("Hours",2,-12),h:Q("Hours",1,-12),mm:Q("Minutes",2),m:Q("Minutes",1),ss:Q("Seconds",2),s:Q("Seconds",1),sss:Q("Milliseconds",3),EEEE:Qa("Day"),EEE:Qa("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=nb(Math[a>0?"floor":"ceil"](a/60),2)+nb(Math.abs(a%60), +2);return c}},nd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,md=/^\d+$/;ac.$inject=["$locale"];var kd=S(I),ld=S(oa);cc.$inject=["$parse"];var rd=S({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),pb={};n(Na,function(a,c){var d=da("ng-"+c);pb[d]=function(){return{priority:100,compile:function(){return function(a, +g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});n(["src","srcset","href"],function(a){var c=da("ng-"+a);pb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ta={$addControl:q,$removeControl:q,$setValidity:q,$setDirty:q,$setPristine:q};fc.$inject=["$element","$attrs","$scope"];var Wa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:fc,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h= +function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};gc(d[0],"submit",h);d.bind("$destroy",function(){c(function(){gb(d[0],"submit",h)},0,!1)})}var j=d.parent().controller("form"),m=i.name||i.ngForm;m&&(a[m]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);m&&(a[m]=p);t(f,Ta)})}}}};return a?t(V(d),{restrict:"EAC"}):d}]},sd=Wa(),td=Wa(!0),ud=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,vd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/, +wd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,hc={text:Va,number:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);e.$parsers.push(function(a){var c=X(a);return c||wd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&ah?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Ya(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||ud.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||vd.test(a)? +(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){C(d.name)&&c.attr("name",Fa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;E(g)||(g=!0);E(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})}); +e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:q,button:q,submit:q,reset:q},ic=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(hc[I(g.type)]||hc.text)(d,e,g,i,c,a)}}}],Sa="ng-valid",Ra="ng-invalid",pa="ng-pristine",Ua="ng-dirty",xd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+bb(c,"-"):""; +e.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),h=f.assign;if(!h)throw Error(Kb+d.ngModel+" ("+va(e)+")");this.$render=q;var j=e.inheritedData("$formController")||Ta,m=0,k=this.$error={};e.addClass(pa);i(!0);this.$setValidity=function(a,c){if(k[a]!==!c){if(c){if(k[a]&&m--,!m)i(!0),this.$valid= +!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,m++;k[a]=!c;i(c,a);j.$setValidity(a,c,this)}};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(Ua).addClass(pa)};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(pa).addClass(Ua),j.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,h(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})}; +var l=this;a.$watch(function(){var c=f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],yd=function(){return{require:["ngModel","^?form"],controller:xd,link:function(a,c,d,e){var g=e[0],i=e[1]||Ta;i.$addControl(g);c.bind("$destroy",function(){i.$removeControl(g)})}}},zd=S({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),jc=function(){return{require:"?ngModel", +link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},Ad=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(U(a))});return c});e.$formatters.push(function(a){return F(a)? +a.join(", "):p})}}},Bd=/^(true|false|\d+)$/,Cd=function(){return{priority:100,compile:function(a,c){return Bd.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},Dd=aa(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),Ed=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding", +c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],Fd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],Gd=ob("",!0),Hd=ob("Odd",0),Id=ob("Even",1),Jd=aa({compile:function(a,c){c.$set("ngCloak",p);a.removeClass("ng-cloak")}}),Kd=[function(){return{scope:!0,controller:"@"}}],Ld=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],kc={};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress".split(" "), +function(a){var c=da("ng-"+a);kc[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(I(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Md=aa(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Nd=["$animator",function(a){return{transclude:"element",priority:1E3,terminal:!0,restrict:"A",compile:function(c,d,e){return function(c,d,f){var h=a(c,f),j,m;c.$watch(f.ngIf,function(a){j&&(h.leave(j),j=p);m&&(m.$destroy(),m=p);ua(a)&&(m=c.$new(),e(m,function(a){j= +a;h.enter(a,d.parent(),d)}))})}}}}],Od=["$http","$templateCache","$anchorScroll","$compile","$animator",function(a,c,d,e,g){return{restrict:"ECA",terminal:!0,compile:function(i,f){var h=f.ngInclude||f.src,j=f.onload||"",m=f.autoscroll;return function(f,i,n){var o=g(f,n),p=0,r,t=function(){r&&(r.$destroy(),r=null);o.leave(i.contents(),i)};f.$watch(h,function(g){var h=++p;g?(a.get(g,{cache:c}).success(function(a){h===p&&(r&&r.$destroy(),r=f.$new(),o.leave(i.contents(),i),a=w("
").html(a).contents(), +o.enter(a,i),e(a)(r),B(m)&&(!m||f.$eval(m))&&d(),r.$emit("$includeContentLoaded"),f.$eval(j))}).error(function(){h===p&&t()}),f.$emit("$includeContentRequested")):t()})}}}}],Pd=aa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Qd=aa({terminal:!0,priority:1E3}),Rd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),j=i.offset||0,m=e.$eval(h),k={},l=c.startSymbol(),p=c.endSymbol();n(m,function(a,e){k[e]= +c(a.replace(d,l+f+"-"+j+p))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in m||(c=a.pluralCat(c-j)),k[c](e,g,!0))},function(a){g.text(a)})}}}],Sd=["$parse","$animator",function(a,c){return{transclude:"element",priority:1E3,terminal:!0,compile:function(d,e,g){return function(d,e,h){var j=c(d,h),m=h.ngRepeat,k=m.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),l,p,o,z,r,t={$id:la};if(!k)throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '"+ +m+"'.");h=k[1];o=k[2];(k=k[4])?(l=a(k),p=function(a,c,e){r&&(t[r]=a);t[z]=c;t.$index=e;return l(d,t)}):p=function(a,c){return la(c)};k=h.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+h+"'.");z=k[3]||k[1];r=k[2];var x={};d.$watchCollection(o,function(a){var c,h,k=e,l,o={},t,q,w,s,B,y,C=[];if(Xa(a))B=a;else{B=[];for(w in a)a.hasOwnProperty(w)&&w.charAt(0)!="$"&&B.push(w);B.sort()}t=B.length;h= +C.length=B.length;for(c=0;c
").html(k).contents();o.enter(k,c);var k=g(k),m=d.current;l=m.scope=a.$new();if(m.controller)f.$scope= +l,f=i(m.controller,f),m.controllerAs&&(l[m.controllerAs]=f),c.children().data("$ngControllerController",f);k(l);l.$emit("$viewContentLoaded");l.$eval(n);e()}else o.leave(c.contents(),c),l&&(l.$destroy(),l=null)}var l,n=m.onload||"",o=f(a,m);a.$on("$routeChangeSuccess",k);k()}}}],ae=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],be=S({terminal:!0}),ce=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/, +e={$setViewValue:q};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var h=this,j={},m=e,k;h.databound=d.ngModel;h.init=function(a,c,d){m=a;k=d};h.addOption=function(c){j[c]=!0;m.$viewValue==c&&(a.val(c),k.parent()&&k.remove())};h.removeOption=function(a){this.hasOption(a)&&(delete j[a],m.$viewValue==a&&this.renderUnknownOption(a))};h.renderUnknownOption=function(c){c="? "+la(c)+" ?";k.val(c);a.prepend(k);a.val(c);k.prop("selected",!0)};h.hasOption= +function(a){return j.hasOwnProperty(a)};c.$on("$destroy",function(){h.renderUnknownOption=q})}],link:function(e,i,f,h){function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(v.parent()&&v.remove(),c.val(a),a===""&&t.prop("selected",!0)):C(a)&&t?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){v.parent()&&v.remove();d.$setViewValue(c.val())})})}function m(a,c,d){var e;d.$render=function(){var a=new za(d.$viewValue);n(c.find("option"),function(c){c.selected= +B(a.get(c.value))})};a.$watch(function(){ia(e,d.$viewValue)||(e=V(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];n(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function k(e,f,g){function i(){var a={"":[]},c=[""],d,h,q,v,s;q=g.$modelValue;v=u(e)||[];var z=l?qb(v):v,B,y,A;y={};s=!1;var C,D;if(o)if(t&&F(q)){s=new za([]);for(h=0;hA;)v.pop().element.remove()}for(;w.length>y;)w.pop()[0].element.remove()} +var h;if(!(h=q.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?' but got '"+q+"'.");var j=c(h[2]||h[1]),k=h[4]||h[6],l=h[5],m=c(h[3]||""),n=c(h[2]?h[1]:k),u=c(h[7]),t=h[8]?c(h[8]):null,w=[[{element:f,label:""}]];r&&(a(r)(e),r.removeClass("ng-scope"),r.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=u(e)||[],d={},h,i,j,m,q,r;if(o){i=[];m=0;for(r=w.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/authz/src/main/resources/static/bootstrap.min.css b/authz/src/main/resources/static/bootstrap.min.css new file mode 100644 index 0000000..cd1c616 --- /dev/null +++ b/authz/src/main/resources/static/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px)and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px)and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px)and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/authz/src/main/resources/static/oauth_test.html b/authz/src/main/resources/static/oauth_test.html new file mode 100644 index 0000000..de93929 --- /dev/null +++ b/authz/src/main/resources/static/oauth_test.html @@ -0,0 +1,251 @@ + + + + + + + + OAuth2-Shiro Test + + +
+ Home + +
+
+

Test from 'code' get 'token' [POST]

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

Test grant_type='password' [POST]

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

Test grant_type='client_credentials' [POST]

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

Test grant_type='refresh_token' [POST]

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

请输入 refresh_token 值

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

返回

+
+
+ +
+
+
+
+ © oauth2-shiro +
+
+
+
+ + \ No newline at end of file diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html new file mode 100644 index 0000000..bbba861 --- /dev/null +++ b/authz/src/main/resources/templates/login.html @@ -0,0 +1,118 @@ + + + + + Login + + + + + +
+

[authz]模块用于管理client_details, user,以及获取access_token去访问 [resources] 模块的资源.

+ + 操作说明 +
    +
  1. +

    登录系统,使用初始的账号 test/test 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

    + +
    +
    +
    + +
    + + +
    + (test) +
    +
    +
    + + +
    + (test) +
    +
    +
    +
    +   + +
    +
    + +
    +
    +
    +
    +
  2. +
  3. +

    + 在开始OAuth2之前, 建议先理解OAuth2支持的5类grant_type, 请访问 https://andaily.com/blog/?p=103, + 或直接去看看OAuth2协议 http://oauth.net/2/ +

    +
  4. +
  5. +

    + 管理client_details, 在初始时创建了默认的管理client_details + test + 与 + mobile + (见initial-db.ddl文件), 你可以去 client_details + 创建新的client_details来测试.
    ----- client_details是OAuth2中一个核心的组件 +

    +
  6. +
  7. +

    + 查看 oauth_test.txt + 文件并进行OAuth2的流程测试; 也可下载 + spring-oauth-client + 项目来测试OAuth2的流程 +

    +
  8. +
+
+ +
+

菜单

+ +
    +
  • +

    + Users -- 管理User +

    +
  • +
  • +

    + client_details -- 管理client_details +

    +
  • +
  • +

    + oauth_test.txt + -- 测试OAuth2的参考URL文件 +

    +
  • +
  • +

    + API + -- oauth2-shiro提供的API文档 +

    +
  • +
  • +

    + oauth_test.html -- + 一个用于测试各类grant_type的HTML页面 +

    +
  • +
+ +
+ + + \ No newline at end of file diff --git a/resources/pom.xml b/resources/pom.xml index b27dcb6..7368eb4 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -83,12 +83,6 @@ org.springframework.boot spring-boot-starter-test test - - - org.junit.vintage - junit-vintage-engine - - -- Gitee From 7b157d8334b8c86d2f56c58a5a7cdfe582ecb636 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 19 Sep 2023 11:51:35 +0800 Subject: [PATCH 16/78] Jsp transfer to html (thymeleaf) - login --- .../os/config/AuthzSecurityConfig.java | 5 +- .../resources/templates/fragments/main.html | 32 +++ authz/src/main/resources/templates/login.html | 207 +++++++++--------- 3 files changed, 142 insertions(+), 102 deletions(-) create mode 100644 authz/src/main/resources/templates/fragments/main.html diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java index 3449eee..733ef4a 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -93,8 +93,9 @@ public class AuthzSecurityConfig { //权限控制 Map map = new HashMap<>(); map.put("/favicon.ico", "anon"); - map.put("/static/**", "anon"); - map.put("/resources/**", "anon"); + map.put("/*.css", "anon"); + map.put("/*.js", "anon"); + map.put("/*.html", "anon"); map.put("/login", "anon"); map.put("/unauthorized", "anon"); // # OAuth anon diff --git a/authz/src/main/resources/templates/fragments/main.html b/authz/src/main/resources/templates/fragments/main.html new file mode 100644 index 0000000..b6d32f6 --- /dev/null +++ b/authz/src/main/resources/templates/fragments/main.html @@ -0,0 +1,32 @@ + + + + + + Fragments + +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html index bbba861..7a08e82 100644 --- a/authz/src/main/resources/templates/login.html +++ b/authz/src/main/resources/templates/login.html @@ -1,118 +1,125 @@ + + + + - Login + Login . OAuth2-Shiro[authz] + + +
+ - +
+

[authz]模块用于管理client_details, user,以及获取access_token去访问 [resources] 模块的资源.

-
-

[authz]模块用于管理client_details, user,以及获取access_token去访问 [resources] 模块的资源.

+ 操作说明 +
    +
  1. +

    登录系统,使用初始的账号 test/test 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

    - 操作说明 -
      -
    1. -

      登录系统,使用初始的账号 test/test 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

      +
      +
      +
      +
      + -
      -
      - - -
      - +
      + (test) +
      +
      +
      + -
      - (test) -
      -
      -
      - +
      + (test) +
      +
      +
      +
      +   + +
      +
      + +
      +
      +
      +
    2. +
    3. +

      + 在开始OAuth2之前, 建议先理解OAuth2支持的5类grant_type, 请访问 https://andaily.com/blog/?p=103, + 或直接去看看OAuth2协议 http://oauth.net/2/ +

      +
    4. +
    5. +

      + 管理client_details, 在初始时创建了默认的管理client_details + test + 与 + mobile + (见initial-db.ddl文件), 你可以去 client_details + 创建新的client_details来测试.
      ----- client_details是OAuth2中一个核心的组件 +

      +
    6. +
    7. +

      + 查看 oauth_test.txt + 文件并进行OAuth2的流程测试; 也可下载 + spring-oauth-client + 项目来测试OAuth2的流程 +

      +
    8. +
    +
-
- (test) -
-
-
-
-   - -
-
- - -
-
-
- -
  • -

    - 在开始OAuth2之前, 建议先理解OAuth2支持的5类grant_type, 请访问 https://andaily.com/blog/?p=103, - 或直接去看看OAuth2协议 http://oauth.net/2/ -

    -
  • -
  • -

    - 管理client_details, 在初始时创建了默认的管理client_details - test - 与 - mobile - (见initial-db.ddl文件), 你可以去 client_details - 创建新的client_details来测试.
    ----- client_details是OAuth2中一个核心的组件 -

    -
  • -
  • -

    - 查看 oauth_test.txt - 文件并进行OAuth2的流程测试; 也可下载 - spring-oauth-client - 项目来测试OAuth2的流程 -

    -
  • - - +
    +

    菜单

    -
    -

    菜单

    +
      +
    • +

      + Users -- 管理User +

      +
    • +
    • +

      + client_details -- 管理client_details +

      +
    • +
    • +

      + oauth_test.txt + -- 测试OAuth2的参考URL文件 +

      +
    • +
    • +

      + API + -- oauth2-shiro提供的API文档 +

      +
    • +
    • +

      + oauth_test.html -- + 一个用于测试各类grant_type的HTML页面 +

      +
    • +
    -
      -
    • -

      - Users -- 管理User -

      -
    • -
    • -

      - client_details -- 管理client_details -

      -
    • -
    • -

      - oauth_test.txt - -- 测试OAuth2的参考URL文件 -

      -
    • -
    • -

      - API - -- oauth2-shiro提供的API文档 -

      -
    • -
    • -

      - oauth_test.html -- - 一个用于测试各类grant_type的HTML页面 -

      -
    • -
    +
    +
    - \ No newline at end of file -- Gitee From bf009b2d85a02df0db3cd7bc2e151c3f10a3b78f Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 19 Sep 2023 14:35:05 +0800 Subject: [PATCH 17/78] Jsp transfer to html (thymeleaf) - index --- authz/pom.xml | 7 +++ .../com/monkeyk/os/config/AuthzWebConfig.java | 12 +++++ .../src/main/resources/application.properties | 2 + authz/src/main/resources/templates/index.html | 48 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 authz/src/main/resources/templates/index.html diff --git a/authz/pom.xml b/authz/pom.xml index 80c4d7a..7befe15 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -85,6 +85,13 @@ spring-boot-starter-thymeleaf + + com.github.theborakompanioni + thymeleaf-extras-shiro + 2.1.0 + + + mysql mysql-connector-java diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java index 285d78a..46479d0 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzWebConfig.java @@ -1,5 +1,6 @@ package com.monkeyk.os.config; +import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.monkeyk.os.web.context.MkkCharacterEncodingFilter; import com.monkeyk.os.web.context.OAuthShiroHandlerExceptionResolver; import org.apache.shiro.mgt.SecurityManager; @@ -82,4 +83,15 @@ public class AuthzWebConfig { return sourceAdvisor; } + + /** + * shiro with thymeleaf ext + * + * @since 2.0.0 + */ + @Bean + public ShiroDialect shiroDialect() { + return new ShiroDialect(); + } + } diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 5fff5b1..7544dc8 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -26,3 +26,5 @@ spring.datasource.hikari.maximum-pool-size=20 spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.cache=false # +server.port=8080 +# diff --git a/authz/src/main/resources/templates/index.html b/authz/src/main/resources/templates/index.html new file mode 100644 index 0000000..5a55f85 --- /dev/null +++ b/authz/src/main/resources/templates/index.html @@ -0,0 +1,48 @@ + + + + + + + + + Home . OAuth2-Shiro[authz] + + + + +
    +

    OAuth2-Shiro is work!

    + Logout +
    + Welcome: + +
    +
    + 测试OAuth + +

    + oauth_test +

    +
    +
    +
    + 菜单 + +

    根据不同的Role会显示不同的菜单 (Shiro权限控制)

    + +
    + +
    +
    + + \ No newline at end of file -- Gitee From e8b8a04f3787f31466f3826a29c32b2e650ebae5 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 20 Sep 2023 12:11:48 +0800 Subject: [PATCH 18/78] Jsp transfer to html (thymeleaf) - user --- authz/src/main/resources/templates/index.html | 2 +- .../resources/templates/unauthorized.html | 22 +++++ .../resources/templates/users/user_plus.html | 83 +++++++++++++++++++ .../templates/users/users_overview.html | 55 ++++++++++++ 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 authz/src/main/resources/templates/unauthorized.html create mode 100644 authz/src/main/resources/templates/users/user_plus.html create mode 100644 authz/src/main/resources/templates/users/users_overview.html diff --git a/authz/src/main/resources/templates/index.html b/authz/src/main/resources/templates/index.html index 5a55f85..bf8307c 100644 --- a/authz/src/main/resources/templates/index.html +++ b/authz/src/main/resources/templates/index.html @@ -16,7 +16,7 @@ Logout
    Welcome: - +
    测试OAuth diff --git a/authz/src/main/resources/templates/unauthorized.html b/authz/src/main/resources/templates/unauthorized.html new file mode 100644 index 0000000..5d2493d --- /dev/null +++ b/authz/src/main/resources/templates/unauthorized.html @@ -0,0 +1,22 @@ + + + + + + + + + Unauthorized . OAuth2-Shiro[authz] + + + + +
    +

    unauthorized

    + Home + Back + +
    +
    + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/users/user_plus.html b/authz/src/main/resources/templates/users/user_plus.html new file mode 100644 index 0000000..0e89150 --- /dev/null +++ b/authz/src/main/resources/templates/users/user_plus.html @@ -0,0 +1,83 @@ + + + + + + + + + Add User . OAuth2-Shiro[authz] + + + + +
    + Home + +

    Add User

    + +
    +
    + + +
    + + +

    Username, unique.

    +
    +
    +
    + + +
    + + +

    Password, required.

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

    Select Roles

    +
    +
    + + +
    +
    +
    + +
    +
    + + +
    +
    +
    + + Cancel +
    +
    +
    + + +
    +
    + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/users/users_overview.html b/authz/src/main/resources/templates/users/users_overview.html new file mode 100644 index 0000000..e834de9 --- /dev/null +++ b/authz/src/main/resources/templates/users/users_overview.html @@ -0,0 +1,55 @@ + + + + + + + + + Users . OAuth2-Shiro[authz] + + + + +
    + Home + +

    Users

    + +
    + Add User +
    +
    +
    + +
    + +  Total: [[${overviewDto.usersSize}]] +
    +
    + + + + + + + + + + + + + + + +
    UsernameRoles[permission]CreateTime
    [[${user.username}]] + + [[${r.roleName}]] [[${r.permissions}]] , + + [[${user.createTime}]]
    + +
    +
    + + \ No newline at end of file -- Gitee From f982fd680da7ff9e032c5dae76d15f5e336ce227 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 20 Sep 2023 15:44:50 +0800 Subject: [PATCH 19/78] Jsp transfer to html (thymeleaf) - client --- .../templates/oauth/client_details.html | 91 +++++++ .../templates/oauth/client_details_plus.html | 252 ++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 authz/src/main/resources/templates/oauth/client_details.html create mode 100644 authz/src/main/resources/templates/oauth/client_details_plus.html diff --git a/authz/src/main/resources/templates/oauth/client_details.html b/authz/src/main/resources/templates/oauth/client_details.html new file mode 100644 index 0000000..1029fa5 --- /dev/null +++ b/authz/src/main/resources/templates/oauth/client_details.html @@ -0,0 +1,91 @@ + + + + + + + + + Client Details . OAuth2-Shiro[authz] + + + + + +
    + Home + +

    Client Details

    + + +
    +
    + +
    + +  Total: [[${listDto.size}]] +
    +
    + +
    +
      + +
    • +

      Empty result

      +
    • + + +
    • +
      + + test + + Archived +
      +

      + [[${cli.clientName}]] + [[${cli.grantTypes}]] +

      + +
      + client_id:   + client_secret:   +
      + grant_types:   + resource_ids:   +
      + scope:   + redirect_uri:   +
      + roles:   + access_token_validity:   + refresh_token_validity:   +
      + client_uri:   + client_icon_uri:   +
      + create_time:   + archived:   + trusted:   + description:   +
      +
    • + + +
    +

    + 每一个item对应oauth_client_details表中的一条数据; 共[[${listDto.size}]]条数据. +

    +
    + +
    +
    + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/client_details_plus.html b/authz/src/main/resources/templates/oauth/client_details_plus.html new file mode 100644 index 0000000..7595c19 --- /dev/null +++ b/authz/src/main/resources/templates/oauth/client_details_plus.html @@ -0,0 +1,252 @@ + + + + + + + + + Add Client . OAuth2-Shiro[authz] + + + + + +
    + Home + +

    Add Client

    + +
    +

    + 这里列出了一个 client_details 需要的所有属性进行添加, 但在实际使用场景中, 许多属性是由系统处理的, 不需要用户关心. +

    + +
    + +
    +
    + + +
    + + +

    client_id必须输入,且必须唯一,长度至少5位; 在实际应用中的另一个名称叫appKey,与client_id是同一个概念.

    +
    +
    +
    + + +
    + + +

    client_secret必须输入,且长度至少8位; + 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.

    +
    +
    +
    + + +
    + + +

    resourceIds, 用于定义一组资源的Id, 在[resources]模块中使用, + 可在[resources]模块的rs-security.xml查看定义的resourceId(每一个 OAuth2Filter 代表一个资源)

    +
    +
    + +
    + + +
    + + +

    scope必须选择

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

    至少勾选一项grant_type(s), 且不能只单独勾选refresh_token, 若需更多帮助请访问 https://andaily.com/blog/?p=103

    +
    +
    + +
    + + +
    + + +

    grant_type包括authorization_code,implicit, + 则必须输入redirect_uri

    +
    +
    + +
    + + +
    + + +

    指定客户端所拥有的Shiro Role,可选; 尚未使用

    +
    +
    + +
    + + +
    + + +

    设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时); + 若设定则必须是大于0的整数值

    +
    +
    + +
    + + +
    + + +

    设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, + 30天); + 若设定则必须是大于0的整数值

    +
    +
    + +
    + + +
    + + +

    给Client起一个名称

    +
    +
    + +
    + + +
    + + +

    该Client的URI, 可选

    +
    +
    +
    + + +
    + + +

    该Client的Icon URI, 可选

    +
    +
    + +
    + + +
    + + +

    Client的描述, 可选

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

    该属性是扩展的, + 只适用于grant_type(s)包括authorization_code的情况,当用户登录成功后,若选No,则会跳转到让用户Approve的页面让用户同意授权, + 若选Yes,则在登录后不需要再让用户Approve同意授权(因为是受信任的)

    +
    +
    + + +
    +
    +
    + +
    +
    + +
    +
    +
    + + 取消 +
    +
    +
    +
    + + +
    +
    + + \ No newline at end of file -- Gitee From 007eedd1682ef19883f7c37371ba87e9e146b66b Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 20 Sep 2023 16:45:32 +0800 Subject: [PATCH 20/78] Jsp transfer to html (thymeleaf) - client --- .../os/service/dto/ClientDetailsFormDto.java | 1 + .../templates/oauth/oauth_approval.html | 48 ++++ .../templates/oauth/oauth_login.html | 89 ++++++++ .../templates/oauth/test_client.html | 209 ++++++++++++++++++ 4 files changed, 347 insertions(+) create mode 100644 authz/src/main/resources/templates/oauth/oauth_approval.html create mode 100644 authz/src/main/resources/templates/oauth/oauth_login.html create mode 100644 authz/src/main/resources/templates/oauth/test_client.html diff --git a/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java b/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java index af0a1ba..3dc0984 100644 --- a/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java +++ b/authz/src/main/java/com/monkeyk/os/service/dto/ClientDetailsFormDto.java @@ -35,6 +35,7 @@ public class ClientDetailsFormDto extends ClientDetailsDto { public ClientDetailsFormDto(List rolesList) { this(); + this.setGrantTypes(""); this.rolesDtoList = RolesDto.toDtos(rolesList); } diff --git a/authz/src/main/resources/templates/oauth/oauth_approval.html b/authz/src/main/resources/templates/oauth/oauth_approval.html new file mode 100644 index 0000000..3084c70 --- /dev/null +++ b/authz/src/main/resources/templates/oauth/oauth_approval.html @@ -0,0 +1,48 @@ + + + + + + + + + OAuth Approval . OAuth2-Shiro[authz] + + + + + +
    +

    OAuth Approval

    + +

    Do you authorize '' to access your protected resources?

    + +
    + + + + + + + + + +
    + +
    + + + + + + + + + +
    + +
    +
    + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/oauth_login.html b/authz/src/main/resources/templates/oauth/oauth_login.html new file mode 100644 index 0000000..9affc4d --- /dev/null +++ b/authz/src/main/resources/templates/oauth/oauth_login.html @@ -0,0 +1,89 @@ + + + + + + + + + OAuth Login . OAuth2-Shiro[authz] + + + + + +
    + +
    +

    OAuth Login

    + +

    + 登录使用的 client_id: ''. +

    + +
    +
    +
    + + + + + + + +
    + + +
    + +
    +
    +
    + + +
    + +
    +
    +
    +
    + +   + Login failed +
    +
    +
    +
    +
    +
    + +
    +
    + +

    可以使用下面的初始账号登录或使用你自行添加的账号:

    + + + + + + + + + + + + + + + + + +
    usernamepasswordgrant_typesroles
    testtestauthorization_code,password,refresh_token,client_credentialsUser(id=22)
    +
    + +
    +
    + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/oauth/test_client.html b/authz/src/main/resources/templates/oauth/test_client.html new file mode 100644 index 0000000..a4e4d41 --- /dev/null +++ b/authz/src/main/resources/templates/oauth/test_client.html @@ -0,0 +1,209 @@ + + + + + + + + + Test [[${clientDetailsDto.clientId}]] . OAuth2-Shiro[authz] + + + + + + +
    +
    + Home + +

    Test [[${clientDetailsDto.clientId}]]

    + +

    + 针对不同的grant_type提供不同的测试URL, + 完整的OAuth测试请访问spring-oauth-client项目. +

    + +
    + +
    +
    Test [authorization_code]
    +
    +

    输入每一步必要的信息后点击其下面的链接地址.

    +
      +
    1. +
      + 从 oauth-shiro 获取 'code' +
      + redirect_uri: +
      +
      + + + + + + + +
      + GET +
      +
    2. +
    3. + 用 'code' 换取 'access_token' +
      + 输入第一步获取的code: +
      + +
      + + + + + + + POST +
      +
    4. +
    +
    +
    + + + +
    +
    Test [password]
    +
    +

    输入username, password 后点击链接地址.

    + username: +
    + password: + +
    + +
    + + + + + + + + POST +
    +

    注意:password模式在OAuth2.1中已不推荐使用(安全系数差)

    +
    +
    + + + +
    +
    Test [implicit]
    +
    +

    输入redirect_uri 后点击链接地址. 获取access_token后注意查看redirect_uri的hash部分(#号后边部分)

    + redirect_uri: + +
    + + +
    + + + + + +
    + GET +
    +
    +
    + + + +
    +
    Test [client_credentials]
    +
    +

    点击链接地址即可测试

    + + +
    + + + + + + POST +
    +
    +
    + + + +
    +
    Test [refresh_token]
    +
    +

    输入refresh_token 后点击链接地址.

    + refresh_token: + +
    + +
    + + + + + + POST +
    +
    +
    + + +
    + Back +
    +
    +
    + + + +
    +
    + + \ No newline at end of file -- Gitee From 1a80b36d5ddd18e05990c785de5f822d35711844 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 20 Sep 2023 17:11:32 +0800 Subject: [PATCH 21/78] move static resource --- .../os/config/AuthzSecurityConfig.java | 6 ++-- .../static/{ => css}/bootstrap.min.css | 0 .../static/{ => html}/OS_API-0.2.html | 2 +- .../static/{ => html}/OS_API-0.3.html | 2 +- .../static/{ => html}/oauth_test.html | 4 +-- .../resources/static/{ => js}/angular.min.js | 0 .../resources/templates/fragments/main.html | 2 +- authz/src/main/resources/templates/index.html | 2 +- authz/src/main/resources/templates/login.html | 4 +-- .../templates/oauth/test_client.html | 30 ++++++++++++------- 10 files changed, 30 insertions(+), 22 deletions(-) rename authz/src/main/resources/static/{ => css}/bootstrap.min.css (100%) rename authz/src/main/resources/static/{ => html}/OS_API-0.2.html (99%) rename authz/src/main/resources/static/{ => html}/OS_API-0.3.html (99%) rename authz/src/main/resources/static/{ => html}/oauth_test.html (99%) rename authz/src/main/resources/static/{ => js}/angular.min.js (100%) diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java index 733ef4a..15ec50e 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -93,9 +93,9 @@ public class AuthzSecurityConfig { //权限控制 Map map = new HashMap<>(); map.put("/favicon.ico", "anon"); - map.put("/*.css", "anon"); - map.put("/*.js", "anon"); - map.put("/*.html", "anon"); + map.put("/css/**", "anon"); + map.put("/js/**", "anon"); + map.put("/html/**", "anon"); map.put("/login", "anon"); map.put("/unauthorized", "anon"); // # OAuth anon diff --git a/authz/src/main/resources/static/bootstrap.min.css b/authz/src/main/resources/static/css/bootstrap.min.css similarity index 100% rename from authz/src/main/resources/static/bootstrap.min.css rename to authz/src/main/resources/static/css/bootstrap.min.css diff --git a/authz/src/main/resources/static/OS_API-0.2.html b/authz/src/main/resources/static/html/OS_API-0.2.html similarity index 99% rename from authz/src/main/resources/static/OS_API-0.2.html rename to authz/src/main/resources/static/html/OS_API-0.2.html index bbd9fbd..3b8cfd5 100644 --- a/authz/src/main/resources/static/OS_API-0.2.html +++ b/authz/src/main/resources/static/html/OS_API-0.2.html @@ -4,7 +4,7 @@ - + oauth2-shiro API - - -
    - - -
    -
    -
    -
    - © 2015-2021 oauth2-shiro -
    -
    -
    -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/index.jsp b/authz/src/main/webapp/WEB-INF/jsp/index.jsp deleted file mode 100644 index 0124cfb..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/index.jsp +++ /dev/null @@ -1,47 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="comm-header.jsp" %> - - - - Home - - -

    Oauth2-Shiro is work!

    -Logout -
    -Welcome: -
    -
    - 测试OAuth - -

    - oauth_test -

    -
    -
    -
    - 菜单 - -

    根据不同的Role会显示不同的菜单 (Shiro权限控制)

    - -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/login.jsp b/authz/src/main/webapp/WEB-INF/jsp/login.jsp deleted file mode 100644 index 4cd4ae6..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/login.jsp +++ /dev/null @@ -1,135 +0,0 @@ -<%-- - * Copyright (c) 2013 Andaily Information Technology Co. Ltd - * www.andaily.com - * All rights reserved. - * - * This software is the confidential and proprietary information of - * Andaily Information Technology Co. Ltd ("Confidential Information"). - * You shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement you - * entered into with Andaily Information Technology Co. Ltd. ---%> -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="comm-header.jsp" %> - - - - - - - Login - - - - - -
    -

    [authz]模块用于管理client_details, user,以及获取access_token去访问 [resources] 模块的资源.

    - - 操作说明 -
      -
    1. -

      登录系统,使用初始的账号 test/test 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

      - -
      -
      - -
      - - -
      - (test) -
      -
      -
      - - -
      - (test) -
      -
      -
      -
      -   - -
      -
      -
      -
      -
      -
      -
    2. -
    3. -

      - 在开始OAuth2之前, 建议先理解OAuth2支持的5类grant_type, 请访问 https://andaily.com/blog/?p=103, - 或直接去看看OAuth2协议 http://oauth.net/2/ -

      -
    4. -
    5. -

      - 管理client_details, 在初始时创建了默认的管理client_details - test - 与 - mobile - (见initial-db.ddl文件), 你可以去 client_details - 创建新的client_details来测试.
      ----- client_details是OAuth2中一个核心的组件 -

      -
    6. -
    7. -

      - 查看 oauth_test.txt - 文件并进行OAuth2的流程测试; 也可下载 - spring-oauth-client - 项目来测试OAuth2的流程 -

      -
    8. -
    -
    - -
    -

    菜单

    - -
      -
    • -

      - Users -- 管理User -

      -
    • -
    • -

      - client_details -- 管理client_details -

      -
    • -
    • -

      - oauth_test.txt - -- 测试OAuth2的参考URL文件 -

      -
    • -
    • -

      - API - -- oauth2-shiro提供的API文档 -

      -
    • -
    • -

      - oauth_test.html -- - 一个用于测试各类grant_type的HTML页面 -

      -
    • -
    - -
    - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp deleted file mode 100644 index f9025c1..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details.jsp +++ /dev/null @@ -1,86 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Client Details - - - -Home - -

    Client Details

    - - -
    -
    - -
    - -  Total: ${listDto.size} -
    -
    - -
    -
      - -
    • -

      Empty result

      -
    • -
      - -
    • -
      - - test - - Archived -
      -

      - ${cli.clientName} - ${cli.grantTypes} -

      - -
      - client_id: ${cli.clientId}  - client_secret: ${cli.clientSecret}  -
      - grant_types: ${cli.grantTypes}  - resource_ids: ${cli.resourceIds}  -
      - scope: ${cli.scope}  - redirect_uri: ${cli.redirectUri}  -
      - roles: ${cli.roles}  - access_token_validity: ${cli.accessTokenValidity}  - refresh_token_validity: ${cli.refreshTokenValidity}  -
      - client_uri: ${cli.clientUri}  - client_icon_uri: ${cli.clientIconUri}  -
      - create_time: ${cli.createTime}  - archived: ${cli.archived}  - trusted: ${cli.trusted}  - description: ${cli.description}  -
      -
    • -
      - -
    -

    - 每一个item对应oauth_client_details表中的一条数据; 共${listDto.size}条数据. -

    -
    - - diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp deleted file mode 100644 index edf5501..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/client_details_plus.jsp +++ /dev/null @@ -1,245 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Add Client - - - -Home - -

    Add Client

    - -
    -

    - 这里列出了一个 client_details 需要的所有属性进行添加, 但在实际使用场景中, 许多属性是由系统处理的, 不需要用户关心. -

    - -
    - -
    - - -
    - - -

    client_id必须输入,且必须唯一,长度至少5位; 在实际应用中的另一个名称叫appKey,与client_id是同一个概念.

    -
    -
    -
    - - -
    - - -

    client_secret必须输入,且长度至少8位; 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.

    -
    -
    -
    - - -
    - - os-resource - mobile-resource - - -

    resourceIds, 用于定义一组资源的Id, 在[resources]模块中使用, - 可在[resources]模块的rs-security.xml查看定义的resourceId(每一个 OAuth2Filter 代表一个资源)

    -
    -
    - -
    - - -
    - - read - write - - -

    scope必须选择

    -
    -
    - -
    - - -
    - - - - - - -

    至少勾选一项grant_type(s), 且不能只单独勾选refresh_token, 若需更多帮助请访问 https://andaily.com/blog/?p=103

    -
    -
    - -
    - - -
    - - -

    grant_type包括authorization_code,implicit, - 则必须输入redirect_uri

    -
    -
    - -
    - - -
    - - - - - -

    指定客户端所拥有的Shiro Role,可选; 尚未使用

    -
    -
    - -
    - - -
    - - -

    设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时); - 若设定则必须是大于0的整数值

    -
    -
    - -
    - - -
    - - -

    设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, - 30天); - 若设定则必须是大于0的整数值

    -
    -
    - -
    - - -
    - - -

    给Client起一个名称

    -
    -
    - -
    - - -
    - - -

    该Client的URI, 可选

    -
    -
    -
    - - -
    - - -

    该Client的Icon URI, 可选

    -
    -
    - -
    - - -
    - - -

    Client的描述, 可选

    -
    -
    - - -
    - - -
    - - - -

    该属性是扩展的, - 只适用于grant_type(s)包括authorization_code的情况,当用户登录成功后,若选No,则会跳转到让用户Approve的页面让用户同意授权, - 若选Yes,则在登录后不需要再让用户Approve同意授权(因为是受信任的)

    -
    -
    -
    - - -
    -
    -
    - -
    -
    - - -
    -
    -
    - - 取消 -
    -
    - -
    - - - - diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp deleted file mode 100644 index a19edc8..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_approval.jsp +++ /dev/null @@ -1,42 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - - - Oauth Approval - -

    OAuth Approval

    - -

    Do you authorize '${param.client_id}' to access your protected resources?

    - -
    - - - - - - - - - -
    - -
    - - - - - - - - - -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp deleted file mode 100644 index 1e7b594..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/oauth_login.jsp +++ /dev/null @@ -1,110 +0,0 @@ -<%-- - * Copyright (c) 2013 Andaily Information Technology Co. Ltd - * www.andaily.com - * All rights reserved. - * - * This software is the confidential and proprietary information of - * Andaily Information Technology Co. Ltd ("Confidential Information"). - * You shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement you - * entered into with Andaily Information Technology Co. Ltd. ---%> -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Oauth Login - - - -
    -

    Oauth Login

    - -

    - 登录使用的 client_id: '${param.client_id}'. -

    - -
    -
    -
    - - - - - - - -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    -
    - -   - Login failed -
    -
    -
    -
    -
    - <%--
    --%> - <%----%> - <%----%> - <%----%> - <%----%> - <%----%> - <%----%> - - <%--Username: --%> - <%--

    --%> - <%--Password: --%> - <%--

    --%> - <%----%> - <%--
    --%> - <%--Login failed--%> - <%--
    --%> -
    - -
    -
    - -

    可以使用下面的账号登录或使用你自行添加的账号:

    - - - - - - - - - - - - - - - - - -
    usernamepasswordgrant_typesroles
    testtestauthorization_code,password,refresh_token,client_credentialsUser(id=22)
    -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp b/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp deleted file mode 100644 index d2e0e23..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/oauth/test_client.jsp +++ /dev/null @@ -1,173 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - Test [${clientDetailsDto.clientId}] - - - - - -
    - Home - -

    Test [${clientDetailsDto.clientId}]

    - -

    - 针对不同的grant_type提供不同的测试URL, - 完整的OAuth测试请访问spring-oauth-client项目. -

    - -
    - -
    -
    Test [authorization_code]
    -
    -

    输入每一步必要的信息后点击其下面的链接地址.

    -
      -
    1. -

      - 从 oauth-shiro 获取 'code' -
      - redirect_uri: -
      - - /oauth/authorize?client_id={{clientId}}&redirect_uri={{redirectUri}}&response_type=code&scope={{scope}}&state=123456 - GET -

      -
    2. -
    3. - 用 'code' 换取 'access_token' -
      - 输入第一步获取的code: -
      - -
      - - POST -
      -
    4. -
    -
    -
    -
    - - -
    -
    Test [password]
    -
    -

    输入username, password 后点击链接地址.

    - username: -
    - password: - -
    - -
    - - POST -
    -
    -
    -
    - - -
    -
    Test [implicit]
    -
    -

    输入redirect_uri 后点击链接地址. 获取access_token后注意查看redirect_uri的hash部分(#号后边部分)

    - redirect_uri: - -

    - /oauth/authorize?client_id={{clientId}}&response_type=token&scope={{scope}}&redirect_uri={{implicitRedirectUri}} - GET -

    -
    -
    -
    - - -
    -
    Test [client_credentials]
    -
    -

    点击链接地址即可测试

    - - -
    - - POST -
    -
    -
    -
    - - -
    -
    Test [refresh_token]
    -
    -

    输入refresh_token 后点击链接地址.

    - refresh_token: - -
    - -
    - - POST -
    -
    -
    -
    - -
    - Back -
    -
    -
    - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp b/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp deleted file mode 100644 index 8cc08b1..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/unauthorized.jsp +++ /dev/null @@ -1,27 +0,0 @@ -<%-- - * Copyright (c) 2013 Andaily Information Technology Co. Ltd - * www.andaily.com - * All rights reserved. - * - * This software is the confidential and proprietary information of - * Andaily Information Technology Co. Ltd ("Confidential Information"). - * You shall not disclose such Confidential Information and shall use - * it only in accordance with the terms of the license agreement you - * entered into with Andaily Information Technology Co. Ltd. ---%> -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> - - - - UnAuthorized - - -

    unauthorized

    -Home -Back - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp b/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp deleted file mode 100644 index 4ce5d23..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/users/user_plus.jsp +++ /dev/null @@ -1,76 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Add User - - -Home - -

    Add User

    - - - -
    - - -
    - - -

    Username, unique.

    -
    -
    -
    - - -
    - - -

    Password, required.

    -
    -
    -
    - - -
    - - - - - - - -

    Select Roles

    -
    -
    - - -
    -
    -
    - -
    -
    - - -
    -
    -
    - - Cancel -
    -
    -
    - - - - diff --git a/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp b/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp deleted file mode 100644 index a23496e..0000000 --- a/authz/src/main/webapp/WEB-INF/jsp/users/users_overview.jsp +++ /dev/null @@ -1,49 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ include file="../comm-header.jsp" %> - - - - Users - - -Home - -

    Users

    - - -
    -
    - -
    - -  Total: ${overviewDto.usersSize} -
    -
    - - - - - - - - - - - - - - - - - -
    UsernameRoles[permission]CreateTime
    ${user.username}${r.roleName} ${r.permissions}${vs.last?'':','}${user.createTime}
    - - diff --git a/authz/src/main/webapp/WEB-INF/log4j.xml.old b/authz/src/main/webapp/WEB-INF/log4j.xml.old deleted file mode 100644 index 8f19edd..0000000 --- a/authz/src/main/webapp/WEB-INF/log4j.xml.old +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old b/authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old deleted file mode 100644 index ce7dffb..0000000 --- a/authz/src/main/webapp/WEB-INF/mkk-servlet.xml.old +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/WEB-INF/web.xml.old b/authz/src/main/webapp/WEB-INF/web.xml.old deleted file mode 100644 index 3c11874..0000000 --- a/authz/src/main/webapp/WEB-INF/web.xml.old +++ /dev/null @@ -1,169 +0,0 @@ - - - - oauth2-shiro - - - - - - - webAppRootKey - oauth2-shiro - - - - - encodingFilter - com.monkeyk.os.web.context.MkkCharacterEncodingFilter - - encoding - UTF-8 - - - forceEncoding - true - - - - encodingFilter - /* - - - - - gzipFilter - - net.sf.ehcache.constructs.web.filter.GzipFilter - - - - gzipFilter - *.css - - - gzipFilter - *.png - - - gzipFilter - *.gif - - - gzipFilter - *.html - - - gzipFilter - *.js - - - - - - shiroFilter - org.springframework.web.filter.DelegatingFilterProxy - - targetFilterLifecycle - true - - - - - shiroFilter - /* - - - - - - sitemesh - com.opensymphony.sitemesh.webapp.SiteMeshFilter - - - sitemesh - /* - - - - ico - image/vnd.microsoft.icon - - - - - contextConfigLocation - classpath:spring/*.xml - - - - - log4jConfigLocation - /WEB-INF/log4j.xml - - - org.springframework.web.util.Log4jConfigListener - - - - - - com.monkeyk.os.web.context.BeanContextLoaderListener - - - - - mkk - org.springframework.web.servlet.DispatcherServlet - 2 - - - mkk - / - - - - - - - - - - - 30 - - - - - loading.jsp - - - - \ No newline at end of file diff --git a/authz/src/main/webapp/favicon.ico b/authz/src/main/webapp/favicon.ico deleted file mode 100644 index 26d798de88313b1e60f65a967249b36022078d07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1150 zcmds#u}VWh5Jhi5uuM=91d$XXVwJ+eLL`+%K0(AHl^+l+LqIWQB32fDfuCXRH;AQ( zRj?3|cy6)-V<2Ly^Ef+qW_I?yx4Tk>r&beVU8f7us-$foMP{1EN!0$Sb@?w-hxMWz zufS%CkrUfzbiF+dHO8AJoEPo51BWR_Y8OWE3Oitne$RbQ@e{h0;w|(GyuluXMgJ`c zYxjAc^-b~CkMIFrGn;G1>)M||zv#$0{|P5BYtbKTEHPmN{H|R4O~3yk><*Fpy=Omt zzvnih8Sit-{Y~O`n74oT)Mo90M6BW3af$Q!`7W4zCWI3^V2pZ6>8Rx5{zU%I`7t|_ Zx#G4Buch9ibWxQq4SKK)Yv1R(?+bddY={5= diff --git a/authz/src/main/webapp/loading.jsp b/authz/src/main/webapp/loading.jsp deleted file mode 100644 index 817b6df..0000000 --- a/authz/src/main/webapp/loading.jsp +++ /dev/null @@ -1,9 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<% - request.getRequestDispatcher("index").forward(request, response); -%> \ No newline at end of file diff --git a/authz/src/main/webapp/resources/OS_API-0.2.html b/authz/src/main/webapp/resources/OS_API-0.2.html deleted file mode 100644 index bbd9fbd..0000000 --- a/authz/src/main/webapp/resources/OS_API-0.2.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - - - - - oauth2-shiro API - - - - - -
    - 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 - public - 的API都是公开的, 其他的API则需要获取 - access_token - 后可调用 -
    - -
    - -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=password) - public -

    - -

    使用grant_type=password方式来获取access_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typepassword固定值
      scope{scope}read or write
      username{username}用户名
      password{password}用户密码
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Bad credentials"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=authorization_code) - public -

    - -

    使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeauthorization_code固定值
      code{code}
      redirect_uri{redirect_uri}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid code - '26964e42c667b5d42f89a1255766630a'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=client_credentials) - public -

    - -

    使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 - refresh_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeclient_credentials固定值
      scope{scope}read or write
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_client","error_description":"Invalid client_id - 'OMN4XjXmJidyzhUGWVrdk'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    刷新access_token (grant_type=refresh_token) - public -

    - -

    用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typerefresh_token固定值
      refresh_token{refresh_token}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid refresh_token: - 8e91a56f53857688a3ffd8c7cfd311cfss"} - -

        -
      • -
      -
    • -
    -
    - -
    - -
    -

    [resources]

    - -

    获取当前系统时间(resource-id: mobile-resource)

    - -

    获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

    - -
      -
    • -

      - 请求URI: /mobile/system_time GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"time":1465560577614} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid access_token: - 95c3afd44c5d87301dc3034b20b3fc75s"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [resources]

    - -

    获取当前用户信息 (resource-id: os-resource; Role: User)

    - -

    使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User - 才能访问

    - -
      -
    • -

      - 请求URI: /rs/username GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid client by token: - 95c3afd44c5d87301dc3034b20b3fc75"} - -

        -
      • -
      -
    • -
    -
    - -
    -
    - - -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/resources/OS_API-0.3.html b/authz/src/main/webapp/resources/OS_API-0.3.html deleted file mode 100644 index 843356a..0000000 --- a/authz/src/main/webapp/resources/OS_API-0.3.html +++ /dev/null @@ -1,636 +0,0 @@ - - - - - - - - oauth2-shiro API - - - - - -
    - 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 - public - 的API都是公开的, 其他的API则需要获取 - access_token - 后可调用 -
    - -
    - -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=password) - public -

    - -

    使用grant_type=password方式来获取access_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typepassword固定值
      scope{scope}read or write
      username{username}用户名
      password{password}用户密码
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Bad credentials"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=authorization_code) - public -

    - -

    使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeauthorization_code固定值
      code{code}
      redirect_uri{redirect_uri}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid code - '26964e42c667b5d42f89a1255766630a'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=token) - public -

    - -

    使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

    - -
      -
    • -

      - 请求URI: /oauth/token GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      grant_typetoken固定值
      scope{scope}read or write
      redirect_uri{redirect_uri}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/authorize?client_id=DYBeQ5lWFKyhY0TDSxxU&response_type=token&scope=read&redirect_uri=http://www.example.com -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - http://www.example.com#token_type=Bearer&expires_in=43199&access_token=fa3bb346a04e20431161771c0003c466 - -

        -
      • -
      -

      通过 redirect_uri的 URL hash 传递access_token信息

      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=client_credentials) - public -

    - -

    使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 - refresh_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeclient_credentials固定值
      scope{scope}read or write
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_client","error_description":"Invalid client_id - 'OMN4XjXmJidyzhUGWVrdk'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    刷新access_token (grant_type=refresh_token) - public -

    - -

    用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typerefresh_token固定值
      refresh_token{refresh_token}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid refresh_token: - 8e91a56f53857688a3ffd8c7cfd311cfss"} - -

        -
      • -
      -
    • -
    -
    - -
    - -
    -

    [resources]

    - -

    获取当前系统时间(resource-id: mobile-resource)

    - -

    获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

    - -
      -
    • -

      - 请求URI: /mobile/system_time GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"time":1465560577614} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid access_token: - 95c3afd44c5d87301dc3034b20b3fc75s"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [resources]

    - -

    获取当前用户信息 (resource-id: os-resource; Role: User)

    - -

    使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User - 才能访问

    - -
      -
    • -

      - 请求URI: /rs/username GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid client by token: - 95c3afd44c5d87301dc3034b20b3fc75"} - -

        -
      • -
      -
    • -
    -
    - -
    -
    - - -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/resources/angular.min.js b/authz/src/main/webapp/resources/angular.min.js deleted file mode 100644 index ac033dc..0000000 --- a/authz/src/main/webapp/resources/angular.min.js +++ /dev/null @@ -1,178 +0,0 @@ -/* - AngularJS v1.1.5 - (c) 2010-2012 Google, Inc. http://angularjs.org - License: MIT -*/ -(function(M,T,p){'use strict';function lc(){var b=M.angular;M.angular=mc;return b}function Xa(b){return!b||typeof b.length!=="number"?!1:typeof b.hasOwnProperty!="function"&&typeof b.constructor!="function"?!0:b instanceof R||ga&&b instanceof ga||Ea.call(b)!=="[object Object]"||typeof b.callee==="function"}function n(b,a,c){var d;if(b)if(H(b))for(d in b)d!="prototype"&&d!="length"&&d!="name"&&b.hasOwnProperty(d)&&a.call(c,b[d],d);else if(b.forEach&&b.forEach!==n)b.forEach(a,c);else if(Xa(b))for(d= -0;d=0&&b.splice(c,1);return a}function V(b,a){if(sa(b)||b&&b.$evalAsync&&b.$watch)throw Error("Can't copy Window or Scope");if(a){if(b===a)throw Error("Can't copy equivalent objects or arrays");if(F(b))for(var c=a.length=0;c2?ka.call(arguments,2):[];return H(a)&&!(a instanceof RegExp)?c.length?function(){return arguments.length?a.apply(b,c.concat(ka.call(arguments,0))):a.apply(b,c)}:function(){return arguments.length?a.apply(b,arguments):a.call(b)}:a}function qc(b,a){var c=a;/^\$+/.test(b)?c=p:sa(a)?c="$WINDOW":a&&T===a?c="$DOCUMENT":a&&a.$evalAsync&& -a.$watch&&(c="$SCOPE");return c}function ha(b,a){return JSON.stringify(b,qc,a?" ":null)}function ub(b){return E(b)?JSON.parse(b):b}function ua(b){b&&b.length!==0?(b=I(""+b),b=!(b=="f"||b=="0"||b=="false"||b=="no"||b=="n"||b=="[]")):b=!1;return b}function va(b){b=w(b).clone();try{b.html("")}catch(a){}var c=w("
    ").append(b).html();try{return b[0].nodeType===3?I(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+I(b)})}catch(d){return I(c)}}function vb(b){var a={},c,d;n((b|| -"").split("&"),function(b){b&&(c=b.split("="),d=decodeURIComponent(c[0]),a[d]=B(c[1])?decodeURIComponent(c[1]):!0)});return a}function wb(b){var a=[];n(b,function(b,d){a.push(wa(d,!0)+(b===!0?"":"="+wa(b,!0)))});return a.length?a.join("&"):""}function ab(b){return wa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function wa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,a?"%20":"+")}function rc(b, -a){function c(a){a&&d.push(a)}var d=[b],e,g,i=["ng:app","ng-app","x-ng-app","data-ng-app"],f=/\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;n(i,function(a){i[a]=!0;c(T.getElementById(a));a=a.replace(":","\\:");b.querySelectorAll&&(n(b.querySelectorAll("."+a),c),n(b.querySelectorAll("."+a+"\\:"),c),n(b.querySelectorAll("["+a+"]"),c))});n(d,function(a){if(!e){var b=f.exec(" "+a.className+" ");b?(e=a,g=(b[2]||"").replace(/\s+/g,",")):n(a.attributes,function(b){if(!e&&i[b.name])e=a,g=b.value})}});e&&a(e,g?[g]:[])} -function xb(b,a){var c=function(){b=w(b);a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);a.unshift("ng");var c=yb(a);c.invoke(["$rootScope","$rootElement","$compile","$injector","$animator",function(a,b,c,d,e){a.$apply(function(){b.data("$injector",d);c(b)(a)});e.enabled(!0)}]);return c},d=/^NG_DEFER_BOOTSTRAP!/;if(M&&!d.test(M.name))return c();M.name=M.name.replace(d,"");Ha.resumeBootstrap=function(b){n(b,function(b){a.push(b)});c()}}function bb(b,a){a=a||"_";return b.replace(sc, -function(b,d){return(d?a:"")+b.toLowerCase()})}function cb(b,a,c){if(!b)throw Error("Argument '"+(a||"?")+"' is "+(c||"required"));return b}function xa(b,a,c){c&&F(b)&&(b=b[b.length-1]);cb(H(b),a,"not a function, got "+(b&&typeof b=="object"?b.constructor.name||"Object":typeof b));return b}function tc(b){function a(a,b,e){return a[b]||(a[b]=e())}return a(a(b,"angular",Object),"module",function(){var b={};return function(d,e,g){e&&b.hasOwnProperty(d)&&(b[d]=null);return a(b,d,function(){function a(c, -d,e){return function(){b[e||"push"]([c,d,arguments]);return m}}if(!e)throw Error("No module: "+d);var b=[],c=[],j=a("$injector","invoke"),m={_invokeQueue:b,_runBlocks:c,requires:e,name:d,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animationProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider", -"directive"),config:j,run:function(a){c.push(a);return this}};g&&j(g);return m})}})}function Ia(b){return b.replace(uc,function(a,b,d,e){return e?d.toUpperCase():d}).replace(vc,"Moz$1")}function db(b,a){function c(){var e;for(var b=[this],c=a,i,f,h,j,m,k;b.length;){i=b.shift();f=0;for(h=i.length;f 
    "+b;a.removeChild(a.firstChild);eb(this,a.childNodes);this.remove()}else eb(this,b)}function fb(b){return b.cloneNode(!0)}function ya(b){zb(b);for(var a=0,b=b.childNodes||[];a-1}function Cb(b,a){a&&n(a.split(" "),function(a){b.className=U((" "+b.className+" ").replace(/[\n\t]/g," ").replace(" "+U(a)+" "," "))})}function Db(b,a){a&&n(a.split(" "),function(a){if(!La(b,a))b.className=U(b.className+" "+U(a))})}function eb(b,a){if(a)for(var a=!a.nodeName&&B(a.length)&&!sa(a)?a:[a],c=0;c 4096 bytes)!")}else{if(h.cookie!==D){D=h.cookie;d=D.split("; ");G={};for(f=0;f0&&(a=unescape(e.substring(0,j)),G[a]===p&&(G[a]=unescape(e.substring(j+1))))}return G}};f.defer=function(a,b){var c;o++;c=k(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};f.defer.cancel=function(a){return u[a]?(delete u[a],l(a),e(q),!0):!1}}function Ec(){this.$get= -["$window","$log","$sniffer","$document",function(b,a,c,d){return new Dc(b,d,a,c)}]}function Fc(){this.$get=function(){function b(b,d){function e(a){if(a!=k){if(l){if(l==a)l=a.n}else l=a;g(a.n,a.p);g(a,k);k=a;k.n=null}}function g(a,b){if(a!=b){if(a)a.p=b;if(b)b.n=a}}if(b in a)throw Error("cacheId "+b+" taken");var i=0,f=t({},d,{id:b}),h={},j=d&&d.capacity||Number.MAX_VALUE,m={},k=null,l=null;return a[b]={put:function(a,b){var c=m[a]||(m[a]={key:a});e(c);if(!C(b))return a in h||i++,h[a]=b,i>j&&this.remove(l.key), -b},get:function(a){var b=m[a];if(b)return e(b),h[a]},remove:function(a){var b=m[a];if(b){if(b==k)k=b.p;if(b==l)l=b.n;g(b.n,b.p);delete m[a];delete h[a];i--}},removeAll:function(){h={};i=0;m={};k=l=null},destroy:function(){m=f=h=null;delete a[b]},info:function(){return t({},f,{size:i})}}}var a={};b.info=function(){var b={};n(a,function(a,e){b[e]=a.info()});return b};b.get=function(b){return a[b]};return b}}function Gc(){this.$get=["$cacheFactory",function(b){return b("templates")}]}function Jb(b){var a= -{},c="Directive",d=/^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,e=/(([\d\w\-_]+)(?:\:([^;]+))?;?)/,g="Template must have exactly one root element. was: ",i=/^\s*(https?|ftp|mailto|file):/;this.directive=function h(d,e){E(d)?(cb(e,"directive"),a.hasOwnProperty(d)||(a[d]=[],b.factory(d+c,["$injector","$exceptionHandler",function(b,c){var e=[];n(a[d],function(a){try{var g=b.invoke(a);if(H(g))g={compile:S(g)};else if(!g.compile&&g.link)g.compile=S(g.link);g.priority=g.priority||0;g.name=g.name||d;g.require= -g.require||g.controller&&g.name;g.restrict=g.restrict||"A";e.push(g)}catch(h){c(h)}});return e}])),a[d].push(e)):n(d,rb(h));return this};this.urlSanitizationWhitelist=function(a){return B(a)?(i=a,this):i};this.$get=["$injector","$interpolate","$exceptionHandler","$http","$templateCache","$parse","$controller","$rootScope","$document",function(b,j,m,k,l,u,o,z,r){function y(a,b,c){a instanceof w||(a=w(a));n(a,function(b,c){b.nodeType==3&&b.nodeValue.match(/\S+/)&&(a[c]=w(b).wrap("").parent()[0])}); -var d=W(a,b,a,c);return function(b,c){cb(b,"scope");for(var e=c?Ba.clone.call(a):a,j=0,g=e.length;js.priority)break;if(t=s.scope)O("isolated scope",K,s,J),L(t)&&(x(J,"ng-isolate-scope"),K=s),x(J,"ng-scope"),r=r||s;A=s.name;if(t=s.controller)q=q||{},O("'"+A+"' controller",q[A],s,J),q[A]=s;if(t=s.transclude)O("transclusion",G,s,J),G=s,l=s.priority,t=="element"?(Y=w(b),J=c.$$element=w(T.createComment(" "+A+": "+c[A]+" ")),b=J[0],ja(e,w(Y[0]),b),P=y(Y,d,l)):(Y=w(fb(b)).contents(), -J.html(""),P=y(Y,d));if(s.template)if(O("template",W,s,J),W=s,t=H(s.template)?s.template(J,c):s.template,t=Lb(t),s.replace){Y=w("
    "+U(t)+"
    ").contents();b=Y[0];if(Y.length!=1||b.nodeType!==1)throw Error(g+t);ja(e,J,b);A={$attr:{}};a=a.concat(v(b,a.splice(B+1,a.length-(B+1)),A));D(c,A);C=a.length}else J.html(t);if(s.templateUrl)O("template",W,s,J),W=s,k=$(a.splice(B,a.length-B),k,J,c,e,s.replace,P),C=a.length;else if(s.compile)try{na=s.compile(J,c,P),H(na)?h(null,na):na&&h(na.pre,na.post)}catch(I){m(I, -va(J))}if(s.terminal)k.terminal=!0,l=Math.max(l,s.priority)}k.scope=r&&r.scope;k.transclude=G&&P;return k}function G(d,e,j,g){var l=!1;if(a.hasOwnProperty(e))for(var k,e=b.get(e+c),i=0,o=e.length;ik.priority)&&k.restrict.indexOf(j)!=-1)d.push(k),l=!0}catch(u){m(u)}return l}function D(a,b){var c=b.$attr,d=a.$attr,e=a.$$element;n(a,function(d,e){e.charAt(0)!="$"&&(b[e]&&(d+=(e==="style"?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});n(b,function(b,j){j=="class"?(x(e,b),a["class"]= -(a["class"]?a["class"]+" ":"")+b):j=="style"?e.attr("style",e.attr("style")+";"+b):j.charAt(0)!="$"&&!a.hasOwnProperty(j)&&(a[j]=b,d[j]=c[j])})}function $(a,b,c,d,e,j,h){var i=[],o,m,u=c[0],z=a.shift(),r=t({},z,{controller:null,templateUrl:null,transclude:null,scope:null}),z=H(z.templateUrl)?z.templateUrl(c,d):z.templateUrl;c.html("");k.get(z,{cache:l}).success(function(l){var k,z,l=Lb(l);if(j){z=w("
    "+U(l)+"
    ").contents();k=z[0];if(z.length!=1||k.nodeType!==1)throw Error(g+l);l={$attr:{}}; -ja(e,c,k);v(k,a,l);D(d,l)}else k=u,c.html(l);a.unshift(r);o=A(a,k,d,h);for(m=W(c[0].childNodes,h);i.length;){var ea=i.shift(),l=i.shift();z=i.shift();var x=i.shift(),y=k;l!==u&&(y=fb(k),ja(z,w(l),y));o(function(){b(m,ea,y,e,x)},ea,y,e,x)}i=null}).error(function(a,b,c,d){throw Error("Failed to load template: "+d.url);});return function(a,c,d,e,j){i?(i.push(c),i.push(d),i.push(e),i.push(j)):o(function(){b(m,c,d,e,j)},c,d,e,j)}}function K(a,b){return b.priority-a.priority}function O(a,b,c,d){if(b)throw Error("Multiple directives ["+ -b.name+", "+c.name+"] asking for "+a+" on: "+va(d));}function P(a,b){var c=j(b,!0);c&&a.push({priority:0,compile:S(function(a,b){var d=b.parent(),e=d.data("$binding")||[];e.push(c);x(d.data("$binding",e),"ng-binding");a.$watch(c,function(a){b[0].nodeValue=a})})})}function s(a,b,c,d){var e=j(c,!0);e&&b.push({priority:100,compile:S(function(a,b,c){b=c.$$observers||(c.$$observers={});if(e=j(c[d],!0))c[d]=e(a),(b[d]||(b[d]=[])).$$inter=!0,(c.$$observers&&c.$$observers[d].$$scope||a).$watch(e,function(a){c.$set(d, -a)})})})}function ja(a,b,c){var d=b[0],e=d.parentNode,j,g;if(a){j=0;for(g=a.length;j0){var e=O[0],f=e.text;if(f==a||f==b||f==c||f==d||!a&&!b&&!c&&!d)return e}return!1}function f(b, -c,d,f){return(b=i(b,c,d,f))?(a&&!b.json&&e("is not valid json",b),O.shift(),b):!1}function h(a){f(a)||e("is unexpected, expecting ["+a+"]",i())}function j(a,b){return t(function(c,d){return a(c,d,b)},{constant:b.constant})}function m(a,b,c){return t(function(d,e){return a(d,e)?b(d,e):c(d,e)},{constant:a.constant&&b.constant&&c.constant})}function k(a,b,c){return t(function(d,e){return b(d,e,a,c)},{constant:a.constant&&c.constant})}function l(){for(var a=[];;)if(O.length>0&&!i("}",")",";","]")&&a.push(w()), -!f(";"))return a.length==1?a[0]:function(b,c){for(var d,e=0;e","<=",">="))a=k(a,b.fn,x());return a}function n(){for(var a=v(),b;b=f("*","/","%");)a=k(a,b.fn,v());return a}function v(){var a;return f("+")?A():(a=f("-"))?k($,a.fn,v()):(a=f("!"))?j(a.fn,v()):A()}function A(){var a;if(f("("))a=w(),h(")");else if(f("["))a=G();else if(f("{"))a=D();else{var b=f();(a= -b.fn)||e("not a primary expression",b);if(b.json)a.constant=a.literal=!0}for(var c;b=f("(","[",".");)b.text==="("?(a=s(a,c),c=null):b.text==="["?(c=a,a=ma(a)):b.text==="."?(c=a,a=ja(a)):e("IMPOSSIBLE");return a}function G(){var a=[],b=!0;if(g().text!="]"){do{var c=P();a.push(c);c.constant||(b=!1)}while(f(","))}h("]");return t(function(b,c){for(var d=[],e=0;e1;d++){var e=a.shift(),g=b[e];g||(g={},b[e]=g);b=g}return b[a.shift()]=c}function ib(b,a,c){if(!a)return b;for(var a=a.split("."),d,e=b,g=a.length,i=0;ia)for(b in g++,e)e.hasOwnProperty(b)&&!f.hasOwnProperty(b)&&(x--,delete e[b])}else e!==f&&(e=f,g++);return g}, -function(){b(f,e,c)})},$digest:function(){var a,d,e,i,u=this.$$asyncQueue,o,z,r=b,n,x=[],p,v;g("$digest");do{z=!1;for(n=this;u.length;)try{n.$eval(u.shift())}catch(A){c(A)}do{if(i=n.$$watchers)for(o=i.length;o--;)try{if(a=i[o],(d=a.get(n))!==(e=a.last)&&!(a.eq?ia(d,e):typeof d=="number"&&typeof e=="number"&&isNaN(d)&&isNaN(e)))z=!0,a.last=a.eq?V(d):d,a.fn(d,e===f?d:e,n),r<5&&(p=4-r,x[p]||(x[p]=[]),v=H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,v+="; newVal: "+ha(d)+"; oldVal: "+ha(e),x[p].push(v))}catch(G){c(G)}if(!(i= -n.$$childHead||n!==this&&n.$$nextSibling))for(;n!==this&&!(i=n.$$nextSibling);)n=n.$parent}while(n=i);if(z&&!r--)throw h.$$phase=null,Error(b+" $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: "+ha(x));}while(z||u.length);h.$$phase=null},$destroy:function(){if(!(h==this||this.$$destroyed)){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(a.$$childHead==this)a.$$childHead=this.$$nextSibling;if(a.$$childTail==this)a.$$childTail=this.$$prevSibling; -if(this.$$prevSibling)this.$$prevSibling.$$nextSibling=this.$$nextSibling;if(this.$$nextSibling)this.$$nextSibling.$$prevSibling=this.$$prevSibling;this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null}},$eval:function(a,b){return d(a)(this,b)},$evalAsync:function(a){this.$$asyncQueue.push(a)},$apply:function(a){try{return g("$apply"),this.$eval(a)}catch(b){c(b)}finally{h.$$phase=null;try{h.$digest()}catch(d){throw c(d),d;}}},$on:function(a,b){var c=this.$$listeners[a]; -c||(this.$$listeners[a]=c=[]);c.push(b);return function(){c[Ga(c,b)]=null}},$emit:function(a,b){var d=[],e,f=this,g=!1,i={name:a,targetScope:f,stopPropagation:function(){g=!0},preventDefault:function(){i.defaultPrevented=!0},defaultPrevented:!1},h=[i].concat(ka.call(arguments,1)),n,x;do{e=f.$$listeners[a]||d;i.currentScope=f;n=0;for(x=e.length;n7),hasEvent:function(a){if(a=="input"&&Z==9)return!1;if(C(c[a])){var b= -e.createElement("div");c[a]="on"+a in b}return c[a]},csp:e.securityPolicy?e.securityPolicy.isActive:!1,vendorPrefix:g,transitions:h,animations:j}}]}function Zc(){this.$get=S(M)}function Wb(b){var a={},c,d,e;if(!b)return a;n(b.split("\n"),function(b){e=b.indexOf(":");c=I(U(b.substr(0,e)));d=U(b.substr(e+1));c&&(a[c]?a[c]+=", "+d:a[c]=d)});return a}function $c(b,a){var c=ad.exec(b);if(c==null)return!0;var d={protocol:c[2],host:c[4],port:N(c[6])||Oa[c[2]]||null,relativeProtocol:c[2]===p||c[2]===""}, -c=jb.exec(a),c={protocol:c[1],host:c[3],port:N(c[5])||Oa[c[1]]||null};return(d.protocol==c.protocol||d.relativeProtocol)&&d.host==c.host&&(d.port==c.port||d.relativeProtocol&&c.port==Oa[c.protocol])}function Xb(b){var a=L(b)?b:p;return function(c){a||(a=Wb(b));return c?a[I(c)]||null:a}}function Yb(b,a,c){if(H(c))return c(b,a);n(c,function(c){b=c(b,a)});return b}function bd(){var b=/^\s*(\[|\{[^\{])/,a=/[\}\]]\s*$/,c=/^\)\]\}',?\n/,d={"Content-Type":"application/json;charset=utf-8"},e=this.defaults= -{transformResponse:[function(d){E(d)&&(d=d.replace(c,""),b.test(d)&&a.test(d)&&(d=ub(d,!0)));return d}],transformRequest:[function(a){return L(a)&&Ea.apply(a)!=="[object File]"?ha(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:d,put:d,patch:d},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},g=this.interceptors=[],i=this.responseInterceptors=[];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(a,b,c,d,k,l){function u(a){function c(a){var b= -t({},a,{data:Yb(a.data,a.headers,d.transformResponse)});return 200<=a.status&&a.status<300?b:k.reject(b)}var d={transformRequest:e.transformRequest,transformResponse:e.transformResponse},f={};t(d,a);d.headers=f;d.method=oa(d.method);t(f,e.headers.common,e.headers[I(d.method)],a.headers);(a=$c(d.url,b.url())?b.cookies()[d.xsrfCookieName||e.xsrfCookieName]:p)&&(f[d.xsrfHeaderName||e.xsrfHeaderName]=a);var g=[function(a){var b=Yb(a.data,Xb(f),a.transformRequest);C(a.data)&&delete f["Content-Type"];if(C(a.withCredentials)&& -!C(e.withCredentials))a.withCredentials=e.withCredentials;return o(a,b,f).then(c,c)},p],j=k.when(d);for(n(y,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;)var a=g.shift(),i=g.shift(),j=j.then(a,i);j.success=function(a){j.then(function(b){a(b.data,b.status,b.headers,d)});return j};j.error=function(a){j.then(null,function(b){a(b.data,b.status,b.headers,d)});return j};return j}function o(b,c,g){function j(a, -b,c){n&&(200<=a&&a<300?n.put(s,[a,b,Wb(c)]):n.remove(s));i(b,a,c);d.$$phase||d.$apply()}function i(a,c,d){c=Math.max(c,0);(200<=c&&c<300?l.resolve:l.reject)({data:a,status:c,headers:Xb(d),config:b})}function h(){var a=Ga(u.pendingRequests,b);a!==-1&&u.pendingRequests.splice(a,1)}var l=k.defer(),o=l.promise,n,p,s=z(b.url,b.params);u.pendingRequests.push(b);o.then(h,h);if((b.cache||e.cache)&&b.cache!==!1&&b.method=="GET")n=L(b.cache)?b.cache:L(e.cache)?e.cache:r;if(n)if(p=n.get(s))if(p.then)return p.then(h, -h),p;else F(p)?i(p[1],p[0],V(p[2])):i(p,200,{});else n.put(s,o);p||a(b.method,s,c,j,g,b.timeout,b.withCredentials,b.responseType);return o}function z(a,b){if(!b)return a;var c=[];nc(b,function(a,b){a==null||a==p||(F(a)||(a=[a]),n(a,function(a){L(a)&&(a=ha(a));c.push(wa(b)+"="+wa(a))}))});return a+(a.indexOf("?")==-1?"?":"&")+c.join("&")}var r=c("$http"),y=[];n(g,function(a){y.unshift(E(a)?l.get(a):l.invoke(a))});n(i,function(a,b){var c=E(a)?l.get(a):l.invoke(a);y.splice(b,0,{response:function(a){return c(k.when(a))}, -responseError:function(a){return c(k.reject(a))}})});u.pendingRequests=[];(function(a){n(arguments,function(a){u[a]=function(b,c){return u(t(c||{},{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){n(arguments,function(a){u[a]=function(b,c,d){return u(t(d||{},{method:a,url:b,data:c}))}})})("post","put");u.defaults=e;return u}]}function cd(){this.$get=["$browser","$window","$document",function(b,a,c){return dd(b,ed,b.defer,a.angular.callbacks,c[0],a.location.protocol.replace(":",""))}]} -function dd(b,a,c,d,e,g){function i(a,b){var c=e.createElement("script"),d=function(){e.body.removeChild(c);b&&b()};c.type="text/javascript";c.src=a;Z?c.onreadystatechange=function(){/loaded|complete/.test(c.readyState)&&d()}:c.onload=c.onerror=d;e.body.appendChild(c);return d}return function(e,h,j,m,k,l,u,o){function z(){p=-1;t&&t();v&&v.abort()}function r(a,d,e,f){var j=(h.match(jb)||["",g])[1];A&&c.cancel(A);t=v=null;d=j=="file"?e?200:404:d;a(d==1223?204:d,e,f);b.$$completeOutstandingRequest(q)} -var p;b.$$incOutstandingRequestCount();h=h||b.url();if(I(e)=="jsonp"){var x="_"+(d.counter++).toString(36);d[x]=function(a){d[x].data=a};var t=i(h.replace("JSON_CALLBACK","angular.callbacks."+x),function(){d[x].data?r(m,200,d[x].data):r(m,p||-2);delete d[x]})}else{var v=new a;v.open(e,h,!0);n(k,function(a,b){a&&v.setRequestHeader(b,a)});v.onreadystatechange=function(){if(v.readyState==4){var a=v.getAllResponseHeaders(),b=["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified", -"Pragma"];a||(a="",n(b,function(b){var c=v.getResponseHeader(b);c&&(a+=b+": "+c+"\n")}));r(m,p||v.status,v.responseType?v.response:v.responseText,a)}};if(u)v.withCredentials=!0;if(o)v.responseType=o;v.send(j||"")}if(l>0)var A=c(z,l);else l&&l.then&&l.then(z)}}function fd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3,lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4", -posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January,February,March,April,May,June,July,August,September,October,November,December".split(","),SHORTMONTH:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(","),DAY:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday".split(","),SHORTDAY:"Sun,Mon,Tue,Wed,Thu,Fri,Sat".split(","),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a",fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y", -mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return b===1?"one":"other"}}}}function gd(){this.$get=["$rootScope","$browser","$q","$exceptionHandler",function(b,a,c,d){function e(e,f,h){var j=c.defer(),m=j.promise,k=B(h)&&!h,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}k||b.$apply()},f),h=function(){delete g[m.$$timeoutId]};m.$$timeoutId=f;g[f]=j;m.then(h,h);return m}var g={};e.cancel=function(b){return b&&b.$$timeoutId in -g?(g[b.$$timeoutId].reject("canceled"),a.defer.cancel(b.$$timeoutId)):!1};return e}]}function Zb(b){function a(a,e){return b.factory(a+c,e)}var c="Filter";this.register=a;this.$get=["$injector",function(a){return function(b){return a.get(b+c)}}];a("currency",$b);a("date",ac);a("filter",hd);a("json",id);a("limitTo",jd);a("lowercase",kd);a("number",bc);a("orderBy",cc);a("uppercase",ld)}function hd(){return function(b,a,c){if(!F(b))return b;var d=[];d.check=function(a){for(var b=0;b-1}}var e=function(a,b){if(typeof b=="string"&&b.charAt(0)==="!")return!e(a,b.substr(1));switch(typeof a){case "boolean":case "number":case "string":return c(a,b);case "object":switch(typeof b){case "object":return c(a,b);default:for(var d in a)if(d.charAt(0)!=="$"&&e(a[d],b))return!0}return!1;case "array":for(d= -0;de+1?i="0":(f=i,j=!0)}if(!j){i=(i.split(ec)[1]||"").length;C(e)&&(e=Math.min(Math.max(a.minFrac,i), -a.maxFrac));var i=Math.pow(10,e),b=Math.round(b*i)/i,b=(""+b).split(ec),i=b[0],b=b[1]||"",j=0,m=a.lgSize,k=a.gSize;if(i.length>=m+k)for(var j=i.length-m,l=0;l0||e>-c)e+=c;e===0&&c==-12&&(e=12);return nb(e,a,d)}}function Qa(b,a){return function(c,d){var e=c["get"+b](),g=oa(a?"SHORT"+b:b);return d[g][e]}}function ac(b){function a(a){var b;if(b=a.match(c)){var a=new Date(0),g=0,i=0,f=b[8]?a.setUTCFullYear:a.setFullYear,h=b[8]?a.setUTCHours:a.setHours;b[9]&&(g=N(b[9]+b[10]),i=N(b[9]+b[11]));f.call(a,N(b[1]),N(b[2])-1,N(b[3]));g=N(b[4]||0)-g;i=N(b[5]||0)-i;f= -N(b[6]||0);b=Math.round(parseFloat("0."+(b[7]||0))*1E3);h.call(a,g,i,f,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,e){var g="",i=[],f,h,e=e||"mediumDate",e=b.DATETIME_FORMATS[e]||e;E(c)&&(c=md.test(c)?N(c):a(c));Ya(c)&&(c=new Date(c));if(!ra(c))return c;for(;e;)(h=nd.exec(e))?(i=i.concat(ka.call(h,1)),e=i.pop()):(i.push(e),e=null);n(i,function(a){f=od[a];g+=f?f(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g, -"").replace(/''/g,"'")});return g}}function id(){return function(b){return ha(b,!0)}}function jd(){return function(b,a){if(!F(b)&&!E(b))return b;a=N(a);if(E(b))return a?a>=0?b.slice(0,a):b.slice(a,b.length):"";var c=[],d,e;a>b.length?a=b.length:a<-b.length&&(a=-b.length);a>0?(d=0,e=a):(d=b.length+a,e=b.length);for(;dl?(d.$setValidity("maxlength",!1),p):(d.$setValidity("maxlength",!0),a)};d.$parsers.push(e);d.$formatters.push(e)}}function ob(b,a){b="ngClass"+b;return aa(function(c,d,e){function g(b){if(a===!0||c.$index%2===a)h&&!ia(b,h)&&i(h),f(b);h=V(b)}function i(a){L(a)&&!F(a)&&(a=Za(a,function(a,b){if(a)return b}));d.removeClass(F(a)?a.join(" "):a)}function f(a){L(a)&&!F(a)&&(a=Za(a, -function(a,b){if(a)return b}));a&&d.addClass(F(a)?a.join(" "):a)}var h=p;c.$watch(e[b],g,!0);e.$observe("class",function(){var a=c.$eval(e[b]);g(a,a)});b!=="ngClass"&&c.$watch("$index",function(d,g){var h=d&1;h!==g&1&&(h===a?f(c.$eval(e[b])):i(c.$eval(e[b])))})})}var I=function(b){return E(b)?b.toLowerCase():b},oa=function(b){return E(b)?b.toUpperCase():b},Z=N((/msie (\d+)/.exec(I(navigator.userAgent))||[])[1]),w,ga,ka=[].slice,Wa=[].push,Ea=Object.prototype.toString,mc=M.angular,Ha=M.angular||(M.angular= -{}),Aa,hb,ba=["0","0","0"];q.$inject=[];qa.$inject=[];hb=Z<9?function(b){b=b.nodeName?b:b[0];return b.scopeName&&b.scopeName!="HTML"?oa(b.scopeName+":"+b.nodeName):b.nodeName}:function(b){return b.nodeName?b.nodeName:b[0].nodeName};var sc=/[A-Z]/g,pd={full:"1.1.5",major:1,minor:1,dot:5,codeName:"triangle-squarification"},Ka=R.cache={},Ja=R.expando="ng-"+(new Date).getTime(),wc=1,gc=M.document.addEventListener?function(b,a,c){b.addEventListener(a,c,!1)}:function(b,a,c){b.attachEvent("on"+a,c)},gb= -M.document.removeEventListener?function(b,a,c){b.removeEventListener(a,c,!1)}:function(b,a,c){b.detachEvent("on"+a,c)},uc=/([\:\-\_]+(.))/g,vc=/^moz([A-Z])/,Ba=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;T.readyState==="complete"?setTimeout(a):(this.bind("DOMContentLoaded",a),R(M).bind("load",a))},toString:function(){var b=[];n(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return b>=0?w(this[b]):w(this[this.length+b])},length:0,push:Wa,sort:[].sort, -splice:[].splice},Na={};n("multiple,selected,checked,disabled,readOnly,required,open".split(","),function(b){Na[I(b)]=b});var Gb={};n("input,select,option,textarea,button,form,details".split(","),function(b){Gb[oa(b)]=!0});n({data:Bb,inheritedData:Ma,scope:function(b){return Ma(b,"$scope")},controller:Eb,injector:function(b){return Ma(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:La,css:function(b,a,c){a=Ia(a);if(B(c))b.style[a]=c;else{var d;Z<=8&&(d=b.currentStyle&&b.currentStyle[a], -d===""&&(d="auto"));d=d||b.style[a];Z<=8&&(d=d===""?p:d);return d}},attr:function(b,a,c){var d=I(a);if(Na[d])if(B(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||q).specified?d:p;else if(B(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),b===null?p:b},prop:function(b,a,c){if(B(c))b[a]=c;else return b[a]},text:t(Z<9?function(b,a){if(b.nodeType==1){if(C(a))return b.innerText;b.innerText=a}else{if(C(a))return b.nodeValue; -b.nodeValue=a}}:function(b,a){if(C(a))return b.textContent;b.textContent=a},{$dv:""}),val:function(b,a){if(C(a))return b.value;b.value=a},html:function(b,a){if(C(a))return b.innerHTML;for(var c=0,d=b.childNodes;c0||parseFloat(h[a+"Duration"])> -0)g="animation",i=a,j=Math.max(parseInt(h[g+"IterationCount"])||0,parseInt(h[i+"IterationCount"])||0,j);f=Math.max(x(h[g+"Delay"]),x(h[i+"Delay"]));g=Math.max(x(h[g+"Duration"]),x(h[i+"Duration"]));d=Math.max(f+j*g,d)}});e.setTimeout(v,d*1E3)}else v()}function v(){if(!v.run)v.run=!0,o(m,r,p),m.removeClass(w),m.removeClass(K),m.removeData(a)}var A=c.$eval(i.ngAnimate),w=A?L(A)?A[j]:A+"-"+j:"",D=d(w),A=D&&D.setup,$=D&&D.start,D=D&&D.cancel;if(w){var K=w+"-active";r||(r=p?p.parent():m.parent());if(!g.transitions&& -!A&&!$||(r.inheritedData(a)||q).running)k(m,r,p),o(m,r,p);else{var O=m.data(a)||{};O.running&&((D||q)(m),O.done());m.data(a,{running:!0,done:v});m.addClass(w);k(m,r,p);if(m.length==0)return v();var P=(A||q)(m);e.setTimeout(t,1)}}else k(m,r,p),o(m,r,p)}}function m(a,c,d){d?d.after(a):c.append(a)}var k={};k.enter=j("enter",m,q);k.leave=j("leave",q,function(a){a.remove()});k.move=j("move",function(a,c,d){m(a,c,d)},q);k.show=j("show",function(a){a.css("display","")},q);k.hide=j("hide",q,function(a){a.css("display", -"none")});k.animate=function(a,c){j(a,q,q)(c)};return k};i.enabled=function(a){if(arguments.length)c.running=!a;return!c.running};return i}]},Kb="Non-assignable model expression: ";Jb.$inject=["$provide"];var Ic=/^(x[\:\-_]|data[\:\-_])/i,jb=/^([^:]+):\/\/(\w+:{0,1}\w*@)?(\{?[\w\.-]*\}?)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/,Pb=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,Oa={http:80,https:443,ftp:21};Rb.prototype=lb.prototype=Qb.prototype={$$replace:!1,absUrl:Pa("$$absUrl"),url:function(a,c){if(C(a))return this.$$url; -var d=Pb.exec(a);d[1]&&this.path(decodeURIComponent(d[1]));if(d[2]||d[1])this.search(d[3]||"");this.hash(d[5]||"",c);return this},protocol:Pa("$$protocol"),host:Pa("$$host"),port:Pa("$$port"),path:Sb("$$path",function(a){return a.charAt(0)=="/"?a:"/"+a}),search:function(a,c){if(C(a))return this.$$search;B(c)?c===null?delete this.$$search[a]:this.$$search[a]=c:this.$$search=E(a)?vb(a):a;this.$$compose();return this},hash:Sb("$$hash",qa),replace:function(){this.$$replace=!0;return this}};var Da={"null":function(){return null}, -"true":function(){return!0},"false":function(){return!1},undefined:q,"+":function(a,c,d,e){d=d(a,c);e=e(a,c);return B(d)?B(e)?d+e:d:B(e)?e:p},"-":function(a,c,d,e){d=d(a,c);e=e(a,c);return(B(d)?d:0)-(B(e)?e:0)},"*":function(a,c,d,e){return d(a,c)*e(a,c)},"/":function(a,c,d,e){return d(a,c)/e(a,c)},"%":function(a,c,d,e){return d(a,c)%e(a,c)},"^":function(a,c,d,e){return d(a,c)^e(a,c)},"=":q,"===":function(a,c,d,e){return d(a,c)===e(a,c)},"!==":function(a,c,d,e){return d(a,c)!==e(a,c)},"==":function(a, -c,d,e){return d(a,c)==e(a,c)},"!=":function(a,c,d,e){return d(a,c)!=e(a,c)},"<":function(a,c,d,e){return d(a,c)":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a,c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"&":function(a,c,d,e){return d(a,c)&e(a,c)},"|":function(a,c,d,e){return e(a,c)(a,c,d(a,c))},"!":function(a,c,d){return!d(a,c)}},Qc={n:"\n",f:"\u000c",r:"\r", -t:"\t",v:"\u000b","'":"'",'"':'"'},mb={},ad=/^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/,ed=M.XMLHttpRequest||function(){try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(a){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(c){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(d){}throw Error("This browser does not support XMLHttpRequest.");};Zb.$inject=["$provide"];$b.$inject=["$locale"];bc.$inject=["$locale"];var ec=".",od={yyyy:Q("FullYear",4),yy:Q("FullYear", -2,0,!0),y:Q("FullYear",1),MMMM:Qa("Month"),MMM:Qa("Month",!0),MM:Q("Month",2,1),M:Q("Month",1,1),dd:Q("Date",2),d:Q("Date",1),HH:Q("Hours",2),H:Q("Hours",1),hh:Q("Hours",2,-12),h:Q("Hours",1,-12),mm:Q("Minutes",2),m:Q("Minutes",1),ss:Q("Seconds",2),s:Q("Seconds",1),sss:Q("Milliseconds",3),EEEE:Qa("Day"),EEE:Qa("Day",!0),a:function(a,c){return a.getHours()<12?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){var a=-1*a.getTimezoneOffset(),c=a>=0?"+":"";c+=nb(Math[a>0?"floor":"ceil"](a/60),2)+nb(Math.abs(a%60), -2);return c}},nd=/((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,md=/^\d+$/;ac.$inject=["$locale"];var kd=S(I),ld=S(oa);cc.$inject=["$parse"];var rd=S({restrict:"E",compile:function(a,c){Z<=8&&(!c.href&&!c.name&&c.$set("href",""),a.append(T.createComment("IE fix")));return function(a,c){c.bind("click",function(a){c.attr("href")||a.preventDefault()})}}}),pb={};n(Na,function(a,c){var d=da("ng-"+c);pb[d]=function(){return{priority:100,compile:function(){return function(a, -g,i){a.$watch(i[d],function(a){i.$set(c,!!a)})}}}}});n(["src","srcset","href"],function(a){var c=da("ng-"+a);pb[c]=function(){return{priority:99,link:function(d,e,g){g.$observe(c,function(c){c&&(g.$set(a,c),Z&&e.prop(a,g[a]))})}}}});var Ta={$addControl:q,$removeControl:q,$setValidity:q,$setDirty:q,$setPristine:q};fc.$inject=["$element","$attrs","$scope"];var Wa=function(a){return["$timeout",function(c){var d={name:"form",restrict:"E",controller:fc,compile:function(){return{pre:function(a,d,i,f){if(!i.action){var h= -function(a){a.preventDefault?a.preventDefault():a.returnValue=!1};gc(d[0],"submit",h);d.bind("$destroy",function(){c(function(){gb(d[0],"submit",h)},0,!1)})}var j=d.parent().controller("form"),m=i.name||i.ngForm;m&&(a[m]=f);j&&d.bind("$destroy",function(){j.$removeControl(f);m&&(a[m]=p);t(f,Ta)})}}}};return a?t(V(d),{restrict:"EAC"}):d}]},sd=Wa(),td=Wa(!0),ud=/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/,vd=/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/, -wd=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/,hc={text:Va,number:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);e.$parsers.push(function(a){var c=X(a);return c||wd.test(a)?(e.$setValidity("number",!0),a===""?null:c?a:parseFloat(a)):(e.$setValidity("number",!1),p)});e.$formatters.push(function(a){return X(a)?"":""+a});if(d.min){var f=parseFloat(d.min),a=function(a){return!X(a)&&ah?(e.$setValidity("max",!1),p):(e.$setValidity("max",!0),a)};e.$parsers.push(d);e.$formatters.push(d)}e.$formatters.push(function(a){return X(a)||Ya(a)?(e.$setValidity("number",!0),a):(e.$setValidity("number",!1),p)})},url:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||ud.test(a)?(e.$setValidity("url",!0),a):(e.$setValidity("url",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},email:function(a,c,d,e,g,i){Va(a,c,d,e,g,i);a=function(a){return X(a)||vd.test(a)? -(e.$setValidity("email",!0),a):(e.$setValidity("email",!1),p)};e.$formatters.push(a);e.$parsers.push(a)},radio:function(a,c,d,e){C(d.name)&&c.attr("name",Fa());c.bind("click",function(){c[0].checked&&a.$apply(function(){e.$setViewValue(d.value)})});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e){var g=d.ngTrueValue,i=d.ngFalseValue;E(g)||(g=!0);E(i)||(i=!1);c.bind("click",function(){a.$apply(function(){e.$setViewValue(c[0].checked)})}); -e.$render=function(){c[0].checked=e.$viewValue};e.$formatters.push(function(a){return a===g});e.$parsers.push(function(a){return a?g:i})},hidden:q,button:q,submit:q,reset:q},ic=["$browser","$sniffer",function(a,c){return{restrict:"E",require:"?ngModel",link:function(d,e,g,i){i&&(hc[I(g.type)]||hc.text)(d,e,g,i,c,a)}}}],Sa="ng-valid",Ra="ng-invalid",pa="ng-pristine",Ua="ng-dirty",xd=["$scope","$exceptionHandler","$attrs","$element","$parse",function(a,c,d,e,g){function i(a,c){c=c?"-"+bb(c,"-"):""; -e.removeClass((a?Ra:Sa)+c).addClass((a?Sa:Ra)+c)}this.$modelValue=this.$viewValue=Number.NaN;this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$name=d.name;var f=g(d.ngModel),h=f.assign;if(!h)throw Error(Kb+d.ngModel+" ("+va(e)+")");this.$render=q;var j=e.inheritedData("$formController")||Ta,m=0,k=this.$error={};e.addClass(pa);i(!0);this.$setValidity=function(a,c){if(k[a]!==!c){if(c){if(k[a]&&m--,!m)i(!0),this.$valid= -!0,this.$invalid=!1}else i(!1),this.$invalid=!0,this.$valid=!1,m++;k[a]=!c;i(c,a);j.$setValidity(a,c,this)}};this.$setPristine=function(){this.$dirty=!1;this.$pristine=!0;e.removeClass(Ua).addClass(pa)};this.$setViewValue=function(d){this.$viewValue=d;if(this.$pristine)this.$dirty=!0,this.$pristine=!1,e.removeClass(pa).addClass(Ua),j.$setDirty();n(this.$parsers,function(a){d=a(d)});if(this.$modelValue!==d)this.$modelValue=d,h(a,d),n(this.$viewChangeListeners,function(a){try{a()}catch(d){c(d)}})}; -var l=this;a.$watch(function(){var c=f(a);if(l.$modelValue!==c){var d=l.$formatters,e=d.length;for(l.$modelValue=c;e--;)c=d[e](c);if(l.$viewValue!==c)l.$viewValue=c,l.$render()}})}],yd=function(){return{require:["ngModel","^?form"],controller:xd,link:function(a,c,d,e){var g=e[0],i=e[1]||Ta;i.$addControl(g);c.bind("$destroy",function(){i.$removeControl(g)})}}},zd=S({require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),jc=function(){return{require:"?ngModel", -link:function(a,c,d,e){if(e){d.required=!0;var g=function(a){if(d.required&&(X(a)||a===!1))e.$setValidity("required",!1);else return e.$setValidity("required",!0),a};e.$formatters.push(g);e.$parsers.unshift(g);d.$observe("required",function(){g(e.$viewValue)})}}}},Ad=function(){return{require:"ngModel",link:function(a,c,d,e){var g=(a=/\/(.*)\//.exec(d.ngList))&&RegExp(a[1])||d.ngList||",";e.$parsers.push(function(a){var c=[];a&&n(a.split(g),function(a){a&&c.push(U(a))});return c});e.$formatters.push(function(a){return F(a)? -a.join(", "):p})}}},Bd=/^(true|false|\d+)$/,Cd=function(){return{priority:100,compile:function(a,c){return Bd.test(c.ngValue)?function(a,c,g){g.$set("value",a.$eval(g.ngValue))}:function(a,c,g){a.$watch(g.ngValue,function(a){g.$set("value",a,!1)})}}}},Dd=aa(function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBind);a.$watch(d.ngBind,function(a){c.text(a==p?"":a)})}),Ed=["$interpolate",function(a){return function(c,d,e){c=a(d.attr(e.$attr.ngBindTemplate));d.addClass("ng-binding").data("$binding", -c);e.$observe("ngBindTemplate",function(a){d.text(a)})}}],Fd=[function(){return function(a,c,d){c.addClass("ng-binding").data("$binding",d.ngBindHtmlUnsafe);a.$watch(d.ngBindHtmlUnsafe,function(a){c.html(a||"")})}}],Gd=ob("",!0),Hd=ob("Odd",0),Id=ob("Even",1),Jd=aa({compile:function(a,c){c.$set("ngCloak",p);a.removeClass("ng-cloak")}}),Kd=[function(){return{scope:!0,controller:"@"}}],Ld=["$sniffer",function(a){return{priority:1E3,compile:function(){a.csp=!0}}}],kc={};n("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress".split(" "), -function(a){var c=da("ng-"+a);kc[c]=["$parse",function(d){return function(e,g,i){var f=d(i[c]);g.bind(I(a),function(a){e.$apply(function(){f(e,{$event:a})})})}}]});var Md=aa(function(a,c,d){c.bind("submit",function(){a.$apply(d.ngSubmit)})}),Nd=["$animator",function(a){return{transclude:"element",priority:1E3,terminal:!0,restrict:"A",compile:function(c,d,e){return function(c,d,f){var h=a(c,f),j,m;c.$watch(f.ngIf,function(a){j&&(h.leave(j),j=p);m&&(m.$destroy(),m=p);ua(a)&&(m=c.$new(),e(m,function(a){j= -a;h.enter(a,d.parent(),d)}))})}}}}],Od=["$http","$templateCache","$anchorScroll","$compile","$animator",function(a,c,d,e,g){return{restrict:"ECA",terminal:!0,compile:function(i,f){var h=f.ngInclude||f.src,j=f.onload||"",m=f.autoscroll;return function(f,i,n){var o=g(f,n),p=0,r,t=function(){r&&(r.$destroy(),r=null);o.leave(i.contents(),i)};f.$watch(h,function(g){var h=++p;g?(a.get(g,{cache:c}).success(function(a){h===p&&(r&&r.$destroy(),r=f.$new(),o.leave(i.contents(),i),a=w("
    ").html(a).contents(), -o.enter(a,i),e(a)(r),B(m)&&(!m||f.$eval(m))&&d(),r.$emit("$includeContentLoaded"),f.$eval(j))}).error(function(){h===p&&t()}),f.$emit("$includeContentRequested")):t()})}}}}],Pd=aa({compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),Qd=aa({terminal:!0,priority:1E3}),Rd=["$locale","$interpolate",function(a,c){var d=/{}/g;return{restrict:"EA",link:function(e,g,i){var f=i.count,h=g.attr(i.$attr.when),j=i.offset||0,m=e.$eval(h),k={},l=c.startSymbol(),p=c.endSymbol();n(m,function(a,e){k[e]= -c(a.replace(d,l+f+"-"+j+p))});e.$watch(function(){var c=parseFloat(e.$eval(f));return isNaN(c)?"":(c in m||(c=a.pluralCat(c-j)),k[c](e,g,!0))},function(a){g.text(a)})}}}],Sd=["$parse","$animator",function(a,c){return{transclude:"element",priority:1E3,terminal:!0,compile:function(d,e,g){return function(d,e,h){var j=c(d,h),m=h.ngRepeat,k=m.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),l,p,o,z,r,t={$id:la};if(!k)throw Error("Expected ngRepeat in form of '_item_ in _collection_[ track by _id_]' but got '"+ -m+"'.");h=k[1];o=k[2];(k=k[4])?(l=a(k),p=function(a,c,e){r&&(t[r]=a);t[z]=c;t.$index=e;return l(d,t)}):p=function(a,c){return la(c)};k=h.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/);if(!k)throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '"+h+"'.");z=k[3]||k[1];r=k[2];var x={};d.$watchCollection(o,function(a){var c,h,k=e,l,o={},t,q,w,s,B,y,C=[];if(Xa(a))B=a;else{B=[];for(w in a)a.hasOwnProperty(w)&&w.charAt(0)!="$"&&B.push(w);B.sort()}t=B.length;h= -C.length=B.length;for(c=0;c
    ").html(k).contents();o.enter(k,c);var k=g(k),m=d.current;l=m.scope=a.$new();if(m.controller)f.$scope= -l,f=i(m.controller,f),m.controllerAs&&(l[m.controllerAs]=f),c.children().data("$ngControllerController",f);k(l);l.$emit("$viewContentLoaded");l.$eval(n);e()}else o.leave(c.contents(),c),l&&(l.$destroy(),l=null)}var l,n=m.onload||"",o=f(a,m);a.$on("$routeChangeSuccess",k);k()}}}],ae=["$templateCache",function(a){return{restrict:"E",terminal:!0,compile:function(c,d){d.type=="text/ng-template"&&a.put(d.id,c[0].text)}}}],be=S({terminal:!0}),ce=["$compile","$parse",function(a,c){var d=/^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w\d]*)|(?:\(\s*([\$\w][\$\w\d]*)\s*,\s*([\$\w][\$\w\d]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/, -e={$setViewValue:q};return{restrict:"E",require:["select","?ngModel"],controller:["$element","$scope","$attrs",function(a,c,d){var h=this,j={},m=e,k;h.databound=d.ngModel;h.init=function(a,c,d){m=a;k=d};h.addOption=function(c){j[c]=!0;m.$viewValue==c&&(a.val(c),k.parent()&&k.remove())};h.removeOption=function(a){this.hasOption(a)&&(delete j[a],m.$viewValue==a&&this.renderUnknownOption(a))};h.renderUnknownOption=function(c){c="? "+la(c)+" ?";k.val(c);a.prepend(k);a.val(c);k.prop("selected",!0)};h.hasOption= -function(a){return j.hasOwnProperty(a)};c.$on("$destroy",function(){h.renderUnknownOption=q})}],link:function(e,i,f,h){function j(a,c,d,e){d.$render=function(){var a=d.$viewValue;e.hasOption(a)?(v.parent()&&v.remove(),c.val(a),a===""&&t.prop("selected",!0)):C(a)&&t?c.val(""):e.renderUnknownOption(a)};c.bind("change",function(){a.$apply(function(){v.parent()&&v.remove();d.$setViewValue(c.val())})})}function m(a,c,d){var e;d.$render=function(){var a=new za(d.$viewValue);n(c.find("option"),function(c){c.selected= -B(a.get(c.value))})};a.$watch(function(){ia(e,d.$viewValue)||(e=V(d.$viewValue),d.$render())});c.bind("change",function(){a.$apply(function(){var a=[];n(c.find("option"),function(c){c.selected&&a.push(c.value)});d.$setViewValue(a)})})}function k(e,f,g){function i(){var a={"":[]},c=[""],d,h,q,v,s;q=g.$modelValue;v=u(e)||[];var z=l?qb(v):v,B,y,A;y={};s=!1;var C,D;if(o)if(t&&F(q)){s=new za([]);for(h=0;hA;)v.pop().element.remove()}for(;w.length>y;)w.pop()[0].element.remove()} -var h;if(!(h=q.match(d)))throw Error("Expected ngOptions in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_ (track by _expr_)?' but got '"+q+"'.");var j=c(h[2]||h[1]),k=h[4]||h[6],l=h[5],m=c(h[3]||""),n=c(h[2]?h[1]:k),u=c(h[7]),t=h[8]?c(h[8]):null,w=[[{element:f,label:""}]];r&&(a(r)(e),r.removeClass("ng-scope"),r.remove());f.html("");f.bind("change",function(){e.$apply(function(){var a,c=u(e)||[],d={},h,i,j,m,q,r;if(o){i=[];m=0;for(r=w.length;m@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak{display:none;}ng\\:form{display:block;}'); diff --git a/authz/src/main/webapp/resources/bootstrap.min.css b/authz/src/main/webapp/resources/bootstrap.min.css deleted file mode 100644 index cd1c616..0000000 --- a/authz/src/main/webapp/resources/bootstrap.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.3.4 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px)and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px)and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px)and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/authz/src/main/webapp/resources/oauth_test.html b/authz/src/main/webapp/resources/oauth_test.html deleted file mode 100644 index de93929..0000000 --- a/authz/src/main/webapp/resources/oauth_test.html +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - - - OAuth2-Shiro Test - - -
    - Home - -
    -
    -

    Test from 'code' get 'token' [POST]

    - -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    -
    - -
    -

    Test grant_type='password' [POST]

    - -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    - -
    - - -
    - -
    -
    -
    -
    -
    - - -
    -
    -

    Test grant_type='client_credentials' [POST]

    - -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    -
    - -
    -

    Test grant_type='refresh_token' [POST]

    - -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - -
    -
    -
    - - -
    - - -

    请输入 refresh_token 值

    -
    -
    -
    - - -
    - -
    -
    -
    -
    -
    - -
    -
    -

    返回

    -
    -
    - -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    -
    - - \ No newline at end of file diff --git a/authz/src/main/webapp/resources/readme.txt b/authz/src/main/webapp/resources/readme.txt deleted file mode 100644 index d01500c..0000000 --- a/authz/src/main/webapp/resources/readme.txt +++ /dev/null @@ -1 +0,0 @@ -All static resources in here. \ No newline at end of file diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java index 85c6c0b..3b26c9b 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java @@ -65,7 +65,7 @@ public class OAuth2JdbcRealm extends MkkJdbcRealm { OAuth2Token upToken = (OAuth2Token) token; final String accessToken = (String) upToken.getCredentials(); - if (StringUtils.isEmpty(accessToken)) { + if (!StringUtils.hasLength(accessToken)) { throw new OAuth2AuthenticationException("Invalid access_token: " + accessToken); } //Validate access token -- Gitee From 3d3bafad9399dbe1b66b1b6638afcb83d45c1e4e Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 20 Sep 2023 18:01:33 +0800 Subject: [PATCH 26/78] update map --- .../java/com/monkeyk/os/config/AuthzSecurityConfig.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java index 15ec50e..d074368 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -14,7 +14,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -90,8 +90,8 @@ public class AuthzSecurityConfig { factoryBean.setLoginUrl("/login"); factoryBean.setSuccessUrl("/index"); factoryBean.setUnauthorizedUrl("/unauthorized"); - //权限控制 - Map map = new HashMap<>(); + //权限控制, map必须要有顺序 + Map map = new LinkedHashMap<>(); map.put("/favicon.ico", "anon"); map.put("/css/**", "anon"); map.put("/js/**", "anon"); -- Gitee From bec55ffcd071b777090e91ad4e3586bd0ae93c36 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 20 Sep 2023 18:02:47 +0800 Subject: [PATCH 27/78] delete unused --- .../resources/spring/authz-context.xml.old | 104 ------------------ .../resources/spring/authz-security.xml.old | 77 ------------- 2 files changed, 181 deletions(-) delete mode 100644 authz/src/main/resources/spring/authz-context.xml.old delete mode 100644 authz/src/main/resources/spring/authz-security.xml.old diff --git a/authz/src/main/resources/spring/authz-context.xml.old b/authz/src/main/resources/spring/authz-context.xml.old deleted file mode 100644 index e15d9f7..0000000 --- a/authz/src/main/resources/spring/authz-context.xml.old +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - classpath:authz.properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/authz/src/main/resources/spring/authz-security.xml.old b/authz/src/main/resources/spring/authz-security.xml.old deleted file mode 100644 index 51e0a24..0000000 --- a/authz/src/main/resources/spring/authz-security.xml.old +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /favicon.ico = anon - /resources/** = anon - /login = anon - /unauthorized = anon - # OAuth anon - /oauth/** = anon - /users/** = anon - /client_details* = anon - /client_details/** = anon - /logout = logout - # admin role - /admin/** = authc, roles["Admin"] - #user permissions - /user/list = authc, perms["user:list"] - /user/create = authc, perms["user:create"] - # everything else requires authentication: - /** = authc - - - - - - \ No newline at end of file -- Gitee From 3766f959baae84a2f3ff312731e185d153672f39 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 11:02:46 +0800 Subject: [PATCH 28/78] RS update --- others/oauth_test.txt | 22 +++++++++---------- .../src/main/resources/application.properties | 4 ++++ 2 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 resources/src/main/resources/application.properties diff --git a/others/oauth_test.txt b/others/oauth_test.txt index 7ae9d4b..3173fc6 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -1,43 +1,43 @@ -Make sure project modules context path as below: -authz -> authz -resources -> rs +Make sure project modules setting as below: +authz -> port: 8080 +resources -> port: 8083 -- Test grant_type = 'authorization_code' , get code -http://localhost:8080/authz/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 +http://localhost:8080/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 -http://localhost:8080/authz/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 +http://localhost:8080/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 -- Test grant_type = 'token' -- implicit -http://localhost:8080/authz/oauth/authorize?response_type=token&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback +http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback -- Test from 'code' get 'token' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http://localhost:8080/os/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 +http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http://localhost:8080/os/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 -- Test grant_type='password' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=password&scope=read write&username=test&password=test +http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=password&scope=read write&username=test&password=test -- Test grant_type='client_credentials' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=client_credentials&scope=read +http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=client_credentials&scope=read -- Test grant_type='refresh_token' [POST] -http://localhost:8080/authz/oauth/token?client_id=test&client_secret=test&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 +http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 -- -Test-Page URL: http://localhost:8080/authz/resources/oauth_test.html +Test-Page URL: http://localhost:8080/html/oauth_test.html -- diff --git a/resources/src/main/resources/application.properties b/resources/src/main/resources/application.properties new file mode 100644 index 0000000..aa8c26b --- /dev/null +++ b/resources/src/main/resources/application.properties @@ -0,0 +1,4 @@ +# +spring.application.name=Resources +# +server.port=8083 -- Gitee From 86e8afe75cd1859fef34e7385f841a4802ca9803 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 11:10:04 +0800 Subject: [PATCH 29/78] RS update readme --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3ef353f..1bdf4d3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 并根据不同的应用场景提供不同的实现(如web场景,移动设备). -该项目与spring-oauth-server实现相同的需求与场合. +该项目与spring-oauth-server实现相同的需求与场合. 只是在实现上使用的技术不同(spring-oauth-server使用Spring Security + spring-security-oauth2实现); 相比spring-oauth-server, oauth2-oltu具有如下特点: @@ -21,7 +21,7 @@

    -OAuth2下一代身份认证授权协议OIDC实现: MyOIDC +OAuth2下一代身份认证授权协议OIDC实现: MyOIDC

    主要技术及版本

    @@ -78,10 +78,10 @@ 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.6.0), 还有MySql(开发用的mysql版本号为5.6)
  • - 下载(或clone)项目到本地 + 下载(或clone)项目到本地
  • - 项目由三个模块(core,authz,resources)组成, core是一个Java项目(jar), authz与resources是Java Web项目(.war) + 项目由三个模块(core,authz,resources)组成, core是一个Java项目(jar), authz与resources是SpringBoot项目(.jar)
  • 创建MySQL数据库(如数据库名 oauth2_shiro), 并运行相应的SQL脚本(脚本文件位于others/database目录), @@ -89,13 +89,13 @@ 运行脚本的顺序: oauth2-shiro.ddl -> initial-db.ddl
  • - 依次修改authz模块的配置文件authz.properties(位于模块的src/main/resources目录)与resources模块的配置文件resources.properties(位于模块的src/main/resources目录); + 依次修改authz模块的配置文件application.properties(位于模块的src/main/resources目录)与resources模块的配置文件application.properties(位于模块的src/main/resources目录); 修改配置文件中的数据库连接信息(包括username, password等), 都连接到数据库oauth2_shiro
  • -将本地项目导入到IDE(如Intellij IDEA)中,配置Tomcat(或类似的servlet运行服务器), 并启动Tomcat(默认端口为8080); +将本地项目导入到IDE(如Intellij IDEA)中, 并启动(authz默认端口为8080,启动类 AuthzApplication.java );
    -注意将项目的 contextPath(根路径) 设置为 'os'. +
    另: 也可通过maven package命令将项目编译为war文件(os.war), 注意编译时每个模块的pom.xml文件中配置的数据库连接信息, 可在Maven命令中添加 -Dmaven.test.skip=true 忽略测试; 将authz模块与resources模块生成的war放在Tomcat中并启动(注意: 这种方式需要将 authz.properties与resources.properties 加入到classpath中并正确配置数据库连接信息). -- Gitee From cd0c444dd4b61b112ef73ea10cf267652762e215 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 11:11:35 +0800 Subject: [PATCH 30/78] RS update readme --- ...45\274\200\345\217\221\350\247\204\350\214\203.txt" | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git "a/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" "b/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" index 18f099c..a678888 100644 --- "a/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" +++ "b/others/\351\241\271\347\233\256\345\274\200\345\217\221\350\247\204\350\214\203.txt" @@ -6,12 +6,10 @@ >> 1.开发使用软件及版本信息 - * JDK 1.7.0_40 - * Maven 3.1.0 - * IDEA 11.1.3 - * Tomcat 7.0.47 - * MySQL 5.6 - * Git 1.7.10 + * JDK 1.8 + * Maven 3.6.0 + * MySQL 5.6+ + * Git 1.8+ 开发平台: Win 7 -- Gitee From 853f4b29f904bb534c4db7fc3b44096275374213 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 11:21:12 +0800 Subject: [PATCH 31/78] Update unit test, authz --- .../src/main/resources/application.properties | 2 -- .../test/java/com/monkeyk/os/ContextTest.java | 6 ++-- .../os/TestApplicationContextInitializer.java | 23 --------------- .../resources/application-test.properties | 28 +++++++++++++++++++ authz/src/test/resources/log4j.properties | 13 --------- authz/src/test/resources/test.properties | 10 ------- 6 files changed, 32 insertions(+), 50 deletions(-) delete mode 100644 authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java create mode 100644 authz/src/test/resources/application-test.properties delete mode 100644 authz/src/test/resources/log4j.properties delete mode 100644 authz/src/test/resources/test.properties diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 7544dc8..3084f84 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -19,8 +19,6 @@ spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.hikari.maximum-pool-size=20 # Logging # -#logging.config=classpath:log4j2.xml -# # # MVC spring.thymeleaf.encoding=UTF-8 diff --git a/authz/src/test/java/com/monkeyk/os/ContextTest.java b/authz/src/test/java/com/monkeyk/os/ContextTest.java index c13d13a..1be068c 100644 --- a/authz/src/test/java/com/monkeyk/os/ContextTest.java +++ b/authz/src/test/java/com/monkeyk/os/ContextTest.java @@ -1,14 +1,16 @@ package com.monkeyk.os; import com.monkeyk.os.domain.shared.BeanProvider; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.BeforeTransaction; /** * @author Shengzhao Li */ -@ContextConfiguration(locations = {"classpath:/spring/*.xml"}, initializers = {TestApplicationContextInitializer.class}) +@SpringBootTest +@ActiveProfiles("test") public abstract class ContextTest extends AbstractTransactionalJUnit4SpringContextTests { diff --git a/authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java b/authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java deleted file mode 100644 index e884f28..0000000 --- a/authz/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.monkeyk.os; - -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.io.ClassPathResource; - -/** - * @author Shengzhao Li - */ -public class TestApplicationContextInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(AbstractApplicationContext applicationContext) { - PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); - //load database.properties - propertyPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); - - applicationContext.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer); - - - } -} \ No newline at end of file diff --git a/authz/src/test/resources/application-test.properties b/authz/src/test/resources/application-test.properties new file mode 100644 index 0000000..1007f8c --- /dev/null +++ b/authz/src/test/resources/application-test.properties @@ -0,0 +1,28 @@ +# authz properties +# +spring.application.name=AuthZ +# +# +# Support deploy to a servlet-container +spring.jmx.enabled=false +#Override default +spring.main.allow-bean-definition-overriding=true +# +#JDBC +#Connection +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# Logging +# +# +# MVC +spring.thymeleaf.encoding=UTF-8 +spring.thymeleaf.cache=false +# +server.port=8080 +# diff --git a/authz/src/test/resources/log4j.properties b/authz/src/test/resources/log4j.properties deleted file mode 100644 index d1deb90..0000000 --- a/authz/src/test/resources/log4j.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u914D\u7F6E\u6839Logger , file, db -log4j.rootLogger=INFO, stdout - -#\u914D\u7F6E\u65E5\u5FD7\u4FE1\u606F\u8F93\u51FA\u76EE\u7684\u5730(appender) -#\u63A7\u5236\u53F0\u8F93\u51FA -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u9009\u9879(\u53EF\u9009) -#log4j.appender.stdout.Threshold=INFO -#log4j.appender.stdout.ImmediateFlush=true -log4j.appender.stdout.Target=System.out -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u683C\u5F0F -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n diff --git a/authz/src/test/resources/test.properties b/authz/src/test/resources/test.properties deleted file mode 100644 index 6044cc4..0000000 --- a/authz/src/test/resources/test.properties +++ /dev/null @@ -1,10 +0,0 @@ -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# localhost -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - - -- Gitee From 0f25e29bf1bec0c13fa64eb8085894856177fd5f Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 12:10:23 +0800 Subject: [PATCH 32/78] RS use config, boot --- resources/pom.xml | 6 - .../monkeyk/os/config/RsContextConfig.java | 43 ++ .../monkeyk/os/config/RsSecurityConfig.java | 150 +++++ .../com/monkeyk/os/config/RsWebConfig.java | 75 +++ .../monkeyk/os/oauth/shiro/OAuth2Filter.java | 2 +- .../os/web/context/BeanContextAware.java | 23 + .../context/BeanContextLoaderListener.java | 2 + .../src/main/resources/application.properties | 12 + resources/src/main/resources/banner.txt | 10 + resources/src/main/resources/logback.xml | 45 ++ .../src/main/resources/logging.properties | 14 - .../src/main/resources/resources.properties | 14 - .../{rs-context.xml => rs-context.xml.old} | 0 .../{rs-security.xml => rs-security.xml.old} | 0 .../{webapp => resources/static}/favicon.ico | Bin resources/src/main/webapp/WEB-INF/log4j.xml | 28 - .../src/main/webapp/WEB-INF/mkk-servlet.xml | 51 -- resources/src/main/webapp/WEB-INF/web.xml | 104 --- resources/src/main/webapp/loading.jsp | 82 --- .../src/main/webapp/static/OS_API-0.2.html | 555 --------------- .../src/main/webapp/static/OS_API-0.3.html | 636 ------------------ .../src/main/webapp/static/bootstrap.min.css | 5 - .../monkeyk/os/ResourcesApplicationTest.java | 1 + 23 files changed, 362 insertions(+), 1496 deletions(-) create mode 100644 resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java create mode 100644 resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java create mode 100644 resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java create mode 100644 resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java create mode 100644 resources/src/main/resources/banner.txt create mode 100644 resources/src/main/resources/logback.xml delete mode 100644 resources/src/main/resources/logging.properties delete mode 100644 resources/src/main/resources/resources.properties rename resources/src/main/resources/spring/{rs-context.xml => rs-context.xml.old} (100%) rename resources/src/main/resources/spring/{rs-security.xml => rs-security.xml.old} (100%) rename resources/src/main/{webapp => resources/static}/favicon.ico (100%) delete mode 100644 resources/src/main/webapp/WEB-INF/log4j.xml delete mode 100644 resources/src/main/webapp/WEB-INF/mkk-servlet.xml delete mode 100644 resources/src/main/webapp/WEB-INF/web.xml delete mode 100644 resources/src/main/webapp/loading.jsp delete mode 100644 resources/src/main/webapp/static/OS_API-0.2.html delete mode 100644 resources/src/main/webapp/static/OS_API-0.3.html delete mode 100644 resources/src/main/webapp/static/bootstrap.min.css diff --git a/resources/pom.xml b/resources/pom.xml index 7368eb4..0de0cb2 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -32,12 +32,6 @@ - - com.mysql.jdbc.Driver - jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&useUnicode=true&characterEncoding=utf8 - andaily - andaily - false diff --git a/resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java new file mode 100644 index 0000000..ad9d1bc --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/config/RsContextConfig.java @@ -0,0 +1,43 @@ +package com.monkeyk.os.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.TransactionManager; + +import javax.sql.DataSource; + +/** + * 2023/9/21 11:30 + *

    + * replaced old rs-context.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class RsContextConfig { + + + + /** + * 事务配置 + */ + @Bean + public TransactionManager transactionManager(DataSource dataSource) { + DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); + transactionManager.setDataSource(dataSource); + return transactionManager; + } + + + @Bean + public JdbcTemplate jdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + +} diff --git a/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java new file mode 100644 index 0000000..afdc85a --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java @@ -0,0 +1,150 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.oauth.shiro.OAuth2Filter; +import com.monkeyk.os.oauth.shiro.OAuth2JdbcRealm; +import com.monkeyk.os.oauth.shiro.OAuth2SubjectFactory; +import com.monkeyk.os.service.OAuthRSService; +import org.apache.shiro.authc.credential.CredentialsMatcher; +import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; +import org.apache.shiro.cache.CacheManager; +import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.realm.Realm; +import org.apache.shiro.realm.jdbc.JdbcRealm; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.servlet.Filter; +import javax.sql.DataSource; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 2023/9/21 11:30 + *

    + * replaced old rs-security.xml + *

    + *

    + * RS(资源服务器)的安全配置, 基于SHIRO; + * 这儿主要是对access_token进行消费(即使用access_token), + * 将对access_token 进行效验, 以及通过access_token获取用户相关信息(如果支持) + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class RsSecurityConfig { + + + @Autowired + private OAuthRSService oAuthRSService; + + /** + * 凭证(access_token)的匹配使用直接比较是否相等 + */ + @Bean + public CredentialsMatcher credentialsMatcher() { + return new SimpleCredentialsMatcher(); + } + + + @Bean + public JdbcRealm jdbcRealm(DataSource dataSource) { + OAuth2JdbcRealm jdbcRealm = new OAuth2JdbcRealm(); + jdbcRealm.setName("jdbcRealm"); + jdbcRealm.setDataSource(dataSource); + jdbcRealm.setCredentialsMatcher(credentialsMatcher()); + jdbcRealm.setPermissionsLookupEnabled(true); + jdbcRealm.setRsService(oAuthRSService); + return jdbcRealm; + } + + + /** + * 若在集群环境下,推荐使用如 redis 之类支持集群的缓存实现 + */ + @Bean + public CacheManager shiroCacheManager() { + return new MemoryConstrainedCacheManager(); + } + + @Bean + public OAuth2SubjectFactory subjectFactory() { + return new OAuth2SubjectFactory(); + } + + @Bean + public DefaultWebSecurityManager securityManager(Realm realm) { + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(realm); + securityManager.setCacheManager(shiroCacheManager()); + securityManager.setSubjectFactory(subjectFactory()); + return securityManager; + } + + /** + * 对于每一个对外提供的Resource(资源), 都需要在此定义, 每个资源对应不同的URL pattern. + * 且有唯一的 resource id, 具体的拦截处理见 OAuth2Filter.java 类 + * 可根据实际需要 进行扩展 + *

    + * Single resource + *

    + * resourceId 在新的OAuth2实现中是可选的了 @since 2.0.0 + */ + @Bean + public OAuth2Filter auth2Filter() { + OAuth2Filter oAuth2Filter = new OAuth2Filter(); + oAuth2Filter.setResourceId("os-resource"); + oAuth2Filter.setRsService(oAuthRSService); + return oAuth2Filter; + } + + /** + * mobile resource + */ + @Bean + public OAuth2Filter mobileOauth2Filter() { + OAuth2Filter oAuth2Filter = new OAuth2Filter(); + oAuth2Filter.setResourceId("mobile-resource"); + oAuth2Filter.setRsService(oAuthRSService); + return oAuth2Filter; + } + + + /** + * SHIRO 核心配置 拦截器(或过滤器) + * 扩展添加自定义的 Resource Filter(如 oauth2Filter) 用于实现对 access_token的校验与处理 + * 在filterChainDefinitions的配置中增加对具体 URL pattern的 OAUTH 保护拦截 + * 可根据实际需求进行扩展 + */ + @Bean + public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { + ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); + bean.setSecurityManager(securityManager); + bean.setLoginUrl("/login"); + bean.setSuccessUrl("/index"); + bean.setUnauthorizedUrl("/unauthorized"); + + Map filters = new LinkedHashMap<>(); + filters.put("oauth", auth2Filter()); + filters.put("mOauth", mobileOauth2Filter()); +// filters.put("oauth2",xxxOauth2Filter()); +// filters.put("oauth3",yyyyyOauth2Filter()); + bean.setFilters(filters); + + Map filterChainDefinitionMap = new LinkedHashMap<>(); + //#oauth + filterChainDefinitionMap.put("/rs/**", "oauth"); + filterChainDefinitionMap.put("/mobile/**", "mOauth"); + // # everything else allow + filterChainDefinitionMap.put("/**", "anon"); + bean.setFilterChainDefinitionMap(filterChainDefinitionMap); + + return bean; + } + + +} diff --git a/resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java new file mode 100644 index 0000000..83919fa --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/config/RsWebConfig.java @@ -0,0 +1,75 @@ +package com.monkeyk.os.config; + +import com.monkeyk.os.web.context.MkkCharacterEncodingFilter; +import com.monkeyk.os.web.context.OAuthShiroHandlerExceptionResolver; +import org.apache.shiro.mgt.SecurityManager; +import org.apache.shiro.spring.LifecycleBeanPostProcessor; +import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; + +/** + * 2023/9/21 12:01 + *

    + * replaced web.xml, mkk-servlet.xml + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Configuration +public class RsWebConfig { + + + /** + * 字符编码配置 UTF-8 + */ + @Bean + public FilterRegistrationBean encodingFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MkkCharacterEncodingFilter()); + registrationBean.addUrlPatterns("/*"); + //值越小越靠前 + registrationBean.setOrder(1); + return registrationBean; + } + + + /** + * 异常处理配置 + */ + @Bean + public HandlerExceptionResolver handlerExceptionResolver() { + return new OAuthShiroHandlerExceptionResolver(); + } + + + /** + * Shiro AOP + */ + @Bean + public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { + return new LifecycleBeanPostProcessor(); + } + + /** + * Enable Shiro Annotations for Spring-configured beans. Only run after + * the lifecycleBeanProcessor has run: + */ +// public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ +// DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); +// autoProxyCre +// return autoProxyCreator; +// } + + + @Bean + public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { + AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor(); + sourceAdvisor.setSecurityManager(securityManager); + return sourceAdvisor; + } + + +} diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java index 500d77c..accafa9 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java @@ -25,7 +25,7 @@ import javax.servlet.http.HttpServletResponse; *

    * 对需要保护的资源进行拦截过滤处理 * 需要与SHIRO的安全整合并加入到SHIRO 流程中 - * 相关配置见 rs-security.xml 文件 + * 相关配置见 RsSecurityConfig.java * * @author Shengzhao Li */ diff --git a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java new file mode 100644 index 0000000..8e6b6af --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -0,0 +1,23 @@ +package com.monkeyk.os.web.context; + +import com.monkeyk.os.domain.shared.BeanProvider; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +/** + * 2020/7/14 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class BeanContextAware implements ApplicationContextAware { + + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + BeanProvider.initialize(applicationContext); + } +} diff --git a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java index c59a2ee..4d30d8b 100644 --- a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java +++ b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextLoaderListener.java @@ -9,7 +9,9 @@ import javax.servlet.ServletContextEvent; /** * @author Shengzhao Li + * @deprecated Use BeanContextAware.java replaced from 2.0.0 */ +@Deprecated public class BeanContextLoaderListener extends ContextLoaderListener { diff --git a/resources/src/main/resources/application.properties b/resources/src/main/resources/application.properties index aa8c26b..a805fce 100644 --- a/resources/src/main/resources/application.properties +++ b/resources/src/main/resources/application.properties @@ -2,3 +2,15 @@ spring.application.name=Resources # server.port=8083 +# +#JDBC configuration information +# connect the same db with authz +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# + diff --git a/resources/src/main/resources/banner.txt b/resources/src/main/resources/banner.txt new file mode 100644 index 0000000..8a6aa6a --- /dev/null +++ b/resources/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +${AnsiColor.BRIGHT_GREEN} + ____ _ _____ _ ____ +/ _ \/ \ /\/__ __\/ \ /|/_ \ +| / \|| | || / \ | |_|| / / +| |-||| \_/| | | | | ||/ /_ +\_/ \|\____/ \_/ \_/ \|\____/ + + +AuthZ: ${application.formatted-version} +Spring Boot: ${spring-boot.formatted-version} diff --git a/resources/src/main/resources/logback.xml b/resources/src/main/resources/logback.xml new file mode 100644 index 0000000..3766fc8 --- /dev/null +++ b/resources/src/main/resources/logback.xml @@ -0,0 +1,45 @@ + + + ${spring.application.name} + + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + true + + + logs/%d{yyyy-MM-dd}/rs-%i.log + 10MB + 15 + + + + + %d{yyyy-MM-dd HH:mm:ss} [%-5level] [%.80c{10}][%L] -%m%n + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/resources/src/main/resources/logging.properties b/resources/src/main/resources/logging.properties deleted file mode 100644 index 1d66b3e..0000000 --- a/resources/src/main/resources/logging.properties +++ /dev/null @@ -1,14 +0,0 @@ -handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler - -############################################################ -# Handler specific properties. -# Describes specific configuration info for Handlers. -# The configuration for Tomcat server -############################################################ - -org.apache.juli.FileHandler.level = FINE -org.apache.juli.FileHandler.directory = ${catalina.base}/logs -org.apache.juli.FileHandler.prefix = error-debug. - -java.util.logging.ConsoleHandler.level = FINE -java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter diff --git a/resources/src/main/resources/resources.properties b/resources/src/main/resources/resources.properties deleted file mode 100644 index 9280944..0000000 --- a/resources/src/main/resources/resources.properties +++ /dev/null @@ -1,14 +0,0 @@ - -# -# ļ, õݿϢҪ authz ģһ -# authz ģ access_token , resources ģaccess_token -# -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# MySQL DB -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - diff --git a/resources/src/main/resources/spring/rs-context.xml b/resources/src/main/resources/spring/rs-context.xml.old similarity index 100% rename from resources/src/main/resources/spring/rs-context.xml rename to resources/src/main/resources/spring/rs-context.xml.old diff --git a/resources/src/main/resources/spring/rs-security.xml b/resources/src/main/resources/spring/rs-security.xml.old similarity index 100% rename from resources/src/main/resources/spring/rs-security.xml rename to resources/src/main/resources/spring/rs-security.xml.old diff --git a/resources/src/main/webapp/favicon.ico b/resources/src/main/resources/static/favicon.ico similarity index 100% rename from resources/src/main/webapp/favicon.ico rename to resources/src/main/resources/static/favicon.ico diff --git a/resources/src/main/webapp/WEB-INF/log4j.xml b/resources/src/main/webapp/WEB-INF/log4j.xml deleted file mode 100644 index 4ce36d7..0000000 --- a/resources/src/main/webapp/WEB-INF/log4j.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/src/main/webapp/WEB-INF/mkk-servlet.xml b/resources/src/main/webapp/WEB-INF/mkk-servlet.xml deleted file mode 100644 index acf290a..0000000 --- a/resources/src/main/webapp/WEB-INF/mkk-servlet.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/src/main/webapp/WEB-INF/web.xml b/resources/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 2bbe0d0..0000000 --- a/resources/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - resources - - - - webAppRootKey - resources - - - - - encodingFilter - com.monkeyk.os.web.context.MkkCharacterEncodingFilter - - encoding - UTF-8 - - - forceEncoding - true - - - - encodingFilter - /* - - - - - - - shiroFilter - org.springframework.web.filter.DelegatingFilterProxy - - targetFilterLifecycle - true - - - - - shiroFilter - /* - - - - - ico - image/vnd.microsoft.icon - - - - - contextConfigLocation - classpath:spring/*.xml - - - - log4jConfigLocation - /WEB-INF/log4j.xml - - - org.springframework.web.util.Log4jConfigListener - - - - - - com.monkeyk.os.web.context.BeanContextLoaderListener - - - - - mkk - org.springframework.web.servlet.DispatcherServlet - 2 - - - mkk - / - - - - - - - - - - 30 - - - - - loading.jsp - - - - \ No newline at end of file diff --git a/resources/src/main/webapp/loading.jsp b/resources/src/main/webapp/loading.jsp deleted file mode 100644 index 694154f..0000000 --- a/resources/src/main/webapp/loading.jsp +++ /dev/null @@ -1,82 +0,0 @@ -<%-- - * - * @author Shengzhao Li ---%> - -<%@ page contentType="text/html;charset=UTF-8" language="java" %> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - - - - - - - - - - - OAuth2-Shiro[resources] - - - - - - -

    - - - -
    -

    - [resources]模块用于提供资源(resource), 是 access_token 的消费者, 在实际应用中, 属于业务系统(认证,安全交给 authz 模块处理), 对其API的调用需要access_token; - 需要先从authz模块获取access_token. -

    - - 操作说明 - -
      -
    1. -

      resources 模块提供了两个API接口,需要不同权限的access_token才可以访问, API分别为:

      - -

      - a. /mobile/system_time -- 获取系统时间 -

      - -

      - b. /rs/username -- 获取用户信息 -

      - -

      - 对API的详细使用请查看文档 OS_API -

      -
    2. -
    3. -

      对于每一类资源(resource), 需要定义一个 resource-id, 并且需要在 rs-security.xml 中配置一个OAuth2Filter, 然后在 - shiroFilter 中添加该 Filter与配置URL Pattern; 详细请参数项目中 rs-security.xml 文件的配置

      -
    4. -
    5. -

      - 注意 [resources]模块的配置文件 resources.properties(位于src/main/resources目录) - 中数据库连接配置信息必须与 [authz]模块中的配置一致. 因为[authz]模块获取token后存入数据库, [resources]模块在调用时从数据库中查询获取. -

      -
    6. -
    -
    - -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    -
    - - \ No newline at end of file diff --git a/resources/src/main/webapp/static/OS_API-0.2.html b/resources/src/main/webapp/static/OS_API-0.2.html deleted file mode 100644 index bbd9fbd..0000000 --- a/resources/src/main/webapp/static/OS_API-0.2.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - - - - - oauth2-shiro API - - - - - -
    - 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 - public - 的API都是公开的, 其他的API则需要获取 - access_token - 后可调用 -
    - -
    - -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=password) - public -

    - -

    使用grant_type=password方式来获取access_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typepassword固定值
      scope{scope}read or write
      username{username}用户名
      password{password}用户密码
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Bad credentials"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=authorization_code) - public -

    - -

    使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeauthorization_code固定值
      code{code}
      redirect_uri{redirect_uri}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid code - '26964e42c667b5d42f89a1255766630a'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=client_credentials) - public -

    - -

    使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 - refresh_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeclient_credentials固定值
      scope{scope}read or write
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_client","error_description":"Invalid client_id - 'OMN4XjXmJidyzhUGWVrdk'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    刷新access_token (grant_type=refresh_token) - public -

    - -

    用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typerefresh_token固定值
      refresh_token{refresh_token}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid refresh_token: - 8e91a56f53857688a3ffd8c7cfd311cfss"} - -

        -
      • -
      -
    • -
    -
    - -
    - -
    -

    [resources]

    - -

    获取当前系统时间(resource-id: mobile-resource)

    - -

    获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

    - -
      -
    • -

      - 请求URI: /mobile/system_time GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"time":1465560577614} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid access_token: - 95c3afd44c5d87301dc3034b20b3fc75s"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [resources]

    - -

    获取当前用户信息 (resource-id: os-resource; Role: User)

    - -

    使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User - 才能访问

    - -
      -
    • -

      - 请求URI: /rs/username GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid client by token: - 95c3afd44c5d87301dc3034b20b3fc75"} - -

        -
      • -
      -
    • -
    -
    - -
    -
    - - -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    - - \ No newline at end of file diff --git a/resources/src/main/webapp/static/OS_API-0.3.html b/resources/src/main/webapp/static/OS_API-0.3.html deleted file mode 100644 index 843356a..0000000 --- a/resources/src/main/webapp/static/OS_API-0.3.html +++ /dev/null @@ -1,636 +0,0 @@ - - - - - - - - oauth2-shiro API - - - - - -
    - 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 - public - 的API都是公开的, 其他的API则需要获取 - access_token - 后可调用 -
    - -
    - -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=password) - public -

    - -

    使用grant_type=password方式来获取access_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typepassword固定值
      scope{scope}read or write
      username{username}用户名
      password{password}用户密码
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Bad credentials"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=authorization_code) - public -

    - -

    使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeauthorization_code固定值
      code{code}
      redirect_uri{redirect_uri}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid code - '26964e42c667b5d42f89a1255766630a'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=token) - public -

    - -

    使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

    - -
      -
    • -

      - 请求URI: /oauth/token GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      grant_typetoken固定值
      scope{scope}read or write
      redirect_uri{redirect_uri}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/authorize?client_id=DYBeQ5lWFKyhY0TDSxxU&response_type=token&scope=read&redirect_uri=http://www.example.com -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - http://www.example.com#token_type=Bearer&expires_in=43199&access_token=fa3bb346a04e20431161771c0003c466 - -

        -
      • -
      -

      通过 redirect_uri的 URL hash 传递access_token信息

      -
    • -
    -
    - -
    -

    [authz]

    - -

    获取access_token (grant_type=client_credentials) - public -

    - -

    使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 - refresh_token

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeclient_credentials固定值
      scope{scope}read or write
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_client","error_description":"Invalid client_id - 'OMN4XjXmJidyzhUGWVrdk'"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [authz]

    - -

    刷新access_token (grant_type=refresh_token) - public -

    - -

    用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

    - -
      -
    • -

      - 请求URI: /oauth/token POST -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typerefresh_token固定值
      refresh_token{refresh_token}
      - 请求示例: -

      - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} - -

        -
      • -
      • -

        - 异常 [400]
        - - {"error":"invalid_grant","error_description":"Invalid refresh_token: - 8e91a56f53857688a3ffd8c7cfd311cfss"} - -

        -
      • -
      -
    • -
    -
    - -
    - -
    -

    [resources]

    - -

    获取当前系统时间(resource-id: mobile-resource)

    - -

    获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

    - -
      -
    • -

      - 请求URI: /mobile/system_time GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"time":1465560577614} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid access_token: - 95c3afd44c5d87301dc3034b20b3fc75s"} - -

        -
      • -
      -
    • -
    -
    - -
    -

    [resources]

    - -

    获取当前用户信息 (resource-id: os-resource; Role: User)

    - -

    使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User - 才能访问

    - -
      -
    • -

      - 请求URI: /rs/username GET -

      - -
      - 请求参数说明: - - - - - - - - - - - - - - -
      参数名参数值必须?备注
      - 请求示例: -

      - http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

      - -
      -
      - - 响应 - -
        -
      • -

        - 正常 [200]
        - - {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} - -

        -
      • -
      • -

        - 异常 [401]
        - - {"error":"invalid_token","error_description":"Invalid client by token: - 95c3afd44c5d87301dc3034b20b3fc75"} - -

        -
      • -
      -
    • -
    -
    - -
    -
    - - -
    -
    -
    -
    - © oauth2-shiro -
    -
    -
    - - \ No newline at end of file diff --git a/resources/src/main/webapp/static/bootstrap.min.css b/resources/src/main/webapp/static/bootstrap.min.css deleted file mode 100644 index cd1c616..0000000 --- a/resources/src/main/webapp/static/bootstrap.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.3.4 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px)and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px)and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px)and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java b/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java index e3cc851..f677272 100644 --- a/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java +++ b/resources/src/test/java/com/monkeyk/os/ResourcesApplicationTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.*; * 2020/7/13 * * @author Shengzhao Li + * @since 2.0.0 */ @SpringBootTest class ResourcesApplicationTest { -- Gitee From 67c76acdf8fc669a1d544e6a6c1ff697a363f908 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 12:11:37 +0800 Subject: [PATCH 33/78] RS use config, boot --- resources/src/main/resources/banner.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/src/main/resources/banner.txt b/resources/src/main/resources/banner.txt index 8a6aa6a..0bdbaaa 100644 --- a/resources/src/main/resources/banner.txt +++ b/resources/src/main/resources/banner.txt @@ -6,5 +6,5 @@ ${AnsiColor.BRIGHT_GREEN} \_/ \|\____/ \_/ \_/ \|\____/ -AuthZ: ${application.formatted-version} +RS: ${application.formatted-version} Spring Boot: ${spring-boot.formatted-version} -- Gitee From e851c5cd753ffa6d181efa2bf4da0a1d05010054 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 12:24:43 +0800 Subject: [PATCH 34/78] RS use config, boot --- .../os/web/controller/WelcomeController.java | 21 +++++ .../src/main/resources/application.properties | 4 +- .../main/resources/spring/rs-context.xml.old | 91 ------------------- .../main/resources/spring/rs-security.xml.old | 85 ----------------- 4 files changed, 24 insertions(+), 177 deletions(-) create mode 100644 resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java delete mode 100644 resources/src/main/resources/spring/rs-context.xml.old delete mode 100644 resources/src/main/resources/spring/rs-security.xml.old diff --git a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java new file mode 100644 index 0000000..ad31241 --- /dev/null +++ b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java @@ -0,0 +1,21 @@ +package com.monkeyk.os.web.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 2023/9/21 12:22 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@RestController +public class WelcomeController { + + + @GetMapping("/") + public String welcome() { + return "RS is working..."; + } + +} diff --git a/resources/src/main/resources/application.properties b/resources/src/main/resources/application.properties index a805fce..a409a9a 100644 --- a/resources/src/main/resources/application.properties +++ b/resources/src/main/resources/application.properties @@ -13,4 +13,6 @@ spring.datasource.password=andaily spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.hikari.maximum-pool-size=20 # - +#Override default +spring.main.allow-bean-definition-overriding=true +# diff --git a/resources/src/main/resources/spring/rs-context.xml.old b/resources/src/main/resources/spring/rs-context.xml.old deleted file mode 100644 index bb96fe6..0000000 --- a/resources/src/main/resources/spring/rs-context.xml.old +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - classpath:resources.properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/resources/src/main/resources/spring/rs-security.xml.old b/resources/src/main/resources/spring/rs-security.xml.old deleted file mode 100644 index cc9103c..0000000 --- a/resources/src/main/resources/spring/rs-security.xml.old +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # oauth - /rs/** = oauth - /mobile/** = mOauth - # everything else allow - /** = anon - - - - - - \ No newline at end of file -- Gitee From 84266bd016e049fee6f0313925f8dc6469048a1c Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 15:31:39 +0800 Subject: [PATCH 35/78] Add Version --- .../os/web/context/BeanContextAware.java | 8 ++++++++ .../java/com/monkeyk/os/domain/Constants.java | 18 ++++++++++++++++++ .../os/infrastructure/ThreadLocalHolder.java | 4 ++-- .../os/web/context/BeanContextAware.java | 10 ++++++++++ .../os/web/controller/WelcomeController.java | 4 +++- 5 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/com/monkeyk/os/domain/Constants.java diff --git a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java index 8e6b6af..e783b47 100644 --- a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java +++ b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -1,11 +1,15 @@ package com.monkeyk.os.web.context; import com.monkeyk.os.domain.shared.BeanProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +import static com.monkeyk.os.domain.Constants.VERSION; + /** * 2020/7/14 * @@ -15,9 +19,13 @@ import org.springframework.stereotype.Component; @Component public class BeanContextAware implements ApplicationContextAware { + private static final Logger LOG = LoggerFactory.getLogger(BeanContextAware.class); @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { BeanProvider.initialize(applicationContext); + if (LOG.isInfoEnabled()) { + LOG.info("Deploy [authz] version: {}", VERSION); + } } } diff --git a/core/src/main/java/com/monkeyk/os/domain/Constants.java b/core/src/main/java/com/monkeyk/os/domain/Constants.java new file mode 100644 index 0000000..e2f8b68 --- /dev/null +++ b/core/src/main/java/com/monkeyk/os/domain/Constants.java @@ -0,0 +1,18 @@ +package com.monkeyk.os.domain; + +/** + * 2023/9/21 15:28 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public interface Constants { + + + /** + * Version + * 与pom.xml中一致 + */ + String VERSION = "2.0.0"; + +} diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java b/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java index cba22ee..2965134 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/ThreadLocalHolder.java @@ -10,14 +10,14 @@ public abstract class ThreadLocalHolder { private static NamedThreadLocal clientIpThreadLocal = new NamedThreadLocal<>("clientIpThreadLocal"); - /* + /** * 设置当前访问的IP地址 * */ public static void clientIp(String clientIp) { clientIpThreadLocal.set(clientIp); } - /* + /** * 获取访问的IP地址 * */ public static String clientIp() { diff --git a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java index 8e6b6af..8b02fa6 100644 --- a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java +++ b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -1,11 +1,15 @@ package com.monkeyk.os.web.context; import com.monkeyk.os.domain.shared.BeanProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +import static com.monkeyk.os.domain.Constants.VERSION; + /** * 2020/7/14 * @@ -15,9 +19,15 @@ import org.springframework.stereotype.Component; @Component public class BeanContextAware implements ApplicationContextAware { + private static final Logger LOG = LoggerFactory.getLogger(BeanContextAware.class); + + @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { BeanProvider.initialize(applicationContext); + if (LOG.isInfoEnabled()) { + LOG.info("Deploy [resources] version: {}", VERSION); + } } } diff --git a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java index ad31241..102da76 100644 --- a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java +++ b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java @@ -3,6 +3,8 @@ package com.monkeyk.os.web.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import static com.monkeyk.os.domain.Constants.VERSION; + /** * 2023/9/21 12:22 * @@ -15,7 +17,7 @@ public class WelcomeController { @GetMapping("/") public String welcome() { - return "RS is working..."; + return "RS is working... v" + VERSION; } } -- Gitee From 1df7744dd522fb41a006ad8b2bd9c4a0adba9a51 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 15:37:07 +0800 Subject: [PATCH 36/78] RS testing --- .../test/java/com/monkeyk/os/ContextTest.java | 6 ++++-- .../test/resources/application-test.properties | 18 ++++++++++++++++++ resources/src/test/resources/log4j.properties | 13 ------------- resources/src/test/resources/test.properties | 10 ---------- 4 files changed, 22 insertions(+), 25 deletions(-) create mode 100644 resources/src/test/resources/application-test.properties delete mode 100644 resources/src/test/resources/log4j.properties delete mode 100644 resources/src/test/resources/test.properties diff --git a/resources/src/test/java/com/monkeyk/os/ContextTest.java b/resources/src/test/java/com/monkeyk/os/ContextTest.java index c13d13a..1be068c 100644 --- a/resources/src/test/java/com/monkeyk/os/ContextTest.java +++ b/resources/src/test/java/com/monkeyk/os/ContextTest.java @@ -1,14 +1,16 @@ package com.monkeyk.os; import com.monkeyk.os.domain.shared.BeanProvider; -import org.springframework.test.context.ContextConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.BeforeTransaction; /** * @author Shengzhao Li */ -@ContextConfiguration(locations = {"classpath:/spring/*.xml"}, initializers = {TestApplicationContextInitializer.class}) +@SpringBootTest +@ActiveProfiles("test") public abstract class ContextTest extends AbstractTransactionalJUnit4SpringContextTests { diff --git a/resources/src/test/resources/application-test.properties b/resources/src/test/resources/application-test.properties new file mode 100644 index 0000000..08b2566 --- /dev/null +++ b/resources/src/test/resources/application-test.properties @@ -0,0 +1,18 @@ +# +spring.application.name=Resources +# +server.port=8083 +# +#JDBC configuration information +# connect the same db with authz +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false +spring.datasource.username=andaily +spring.datasource.password=andaily +#Datasource properties +spring.datasource.type=com.zaxxer.hikari.HikariDataSource +spring.datasource.hikari.maximum-pool-size=20 +# +#Override default +spring.main.allow-bean-definition-overriding=true +# diff --git a/resources/src/test/resources/log4j.properties b/resources/src/test/resources/log4j.properties deleted file mode 100644 index d1deb90..0000000 --- a/resources/src/test/resources/log4j.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u914D\u7F6E\u6839Logger , file, db -log4j.rootLogger=INFO, stdout - -#\u914D\u7F6E\u65E5\u5FD7\u4FE1\u606F\u8F93\u51FA\u76EE\u7684\u5730(appender) -#\u63A7\u5236\u53F0\u8F93\u51FA -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u9009\u9879(\u53EF\u9009) -#log4j.appender.stdout.Threshold=INFO -#log4j.appender.stdout.ImmediateFlush=true -log4j.appender.stdout.Target=System.out -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u683C\u5F0F -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n diff --git a/resources/src/test/resources/test.properties b/resources/src/test/resources/test.properties deleted file mode 100644 index 6044cc4..0000000 --- a/resources/src/test/resources/test.properties +++ /dev/null @@ -1,10 +0,0 @@ -#JDBC configuration information -jdbc.driverClassName=com.mysql.jdbc.Driver -############ -# localhost -############ -jdbc.url=jdbc:mysql://localhost:3306/oauth2_shiro_test?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8 -jdbc.username=andaily -jdbc.password=andaily - - -- Gitee From 3a6d4cd0f28fb03ded9a709ed28c8bd3cb09207a Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 15:39:12 +0800 Subject: [PATCH 37/78] RS testing --- .../os/TestApplicationContextInitializer.java | 23 ------------------- core/src/test/resources/log4j.properties | 13 ----------- .../os/TestApplicationContextInitializer.java | 23 ------------------- 3 files changed, 59 deletions(-) delete mode 100644 core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java delete mode 100644 core/src/test/resources/log4j.properties delete mode 100644 resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java diff --git a/core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java b/core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java deleted file mode 100644 index e884f28..0000000 --- a/core/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.monkeyk.os; - -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.io.ClassPathResource; - -/** - * @author Shengzhao Li - */ -public class TestApplicationContextInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(AbstractApplicationContext applicationContext) { - PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); - //load database.properties - propertyPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); - - applicationContext.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer); - - - } -} \ No newline at end of file diff --git a/core/src/test/resources/log4j.properties b/core/src/test/resources/log4j.properties deleted file mode 100644 index d1deb90..0000000 --- a/core/src/test/resources/log4j.properties +++ /dev/null @@ -1,13 +0,0 @@ -#\u914D\u7F6E\u6839Logger , file, db -log4j.rootLogger=INFO, stdout - -#\u914D\u7F6E\u65E5\u5FD7\u4FE1\u606F\u8F93\u51FA\u76EE\u7684\u5730(appender) -#\u63A7\u5236\u53F0\u8F93\u51FA -log4j.appender.stdout=org.apache.log4j.ConsoleAppender -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u9009\u9879(\u53EF\u9009) -#log4j.appender.stdout.Threshold=INFO -#log4j.appender.stdout.ImmediateFlush=true -log4j.appender.stdout.Target=System.out -#\u63A7\u5236\u53F0\u8F93\u51FA\u7684\u683C\u5F0F -log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n diff --git a/resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java b/resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java deleted file mode 100644 index e884f28..0000000 --- a/resources/src/test/java/com/monkeyk/os/TestApplicationContextInitializer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.monkeyk.os; - -import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.io.ClassPathResource; - -/** - * @author Shengzhao Li - */ -public class TestApplicationContextInitializer implements ApplicationContextInitializer { - - @Override - public void initialize(AbstractApplicationContext applicationContext) { - PropertyPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertyPlaceholderConfigurer(); - //load database.properties - propertyPlaceholderConfigurer.setLocation(new ClassPathResource("test.properties")); - - applicationContext.addBeanFactoryPostProcessor(propertyPlaceholderConfigurer); - - - } -} \ No newline at end of file -- Gitee From 578a444a8292ada7ea5c42310e511da9d4774dde Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 18:36:36 +0800 Subject: [PATCH 38/78] oauth test --- .../web/controller/OauthTokenController.java | 5 +- others/oauth_test.txt | 75 ++++++++++++++++--- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java b/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java index 210d03c..0d75d6e 100644 --- a/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java +++ b/authz/src/main/java/com/monkeyk/os/web/controller/OauthTokenController.java @@ -8,6 +8,7 @@ import org.apache.oltu.oauth2.common.exception.OAuthProblemException; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @@ -35,9 +36,9 @@ public class OauthTokenController { * * @param request HttpServletRequest * @param response HttpServletResponse - * @throws OAuthSystemException + * @throws OAuthSystemException e */ - @RequestMapping("token") + @PostMapping("token") public void authorize(HttpServletRequest request, HttpServletResponse response) throws OAuthSystemException { try { OAuthTokenxRequest tokenRequest = new OAuthTokenxRequest(request); diff --git a/others/oauth_test.txt b/others/oauth_test.txt index 3173fc6..cb7c35a 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -4,37 +4,92 @@ authz -> port: 8080 resources -> port: 8083 --- Test grant_type = 'authorization_code' , get code +-- Test grant_type = 'authorization_code' , get code [GET] http://localhost:8080/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 http://localhost:8080/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 --- Test grant_type = 'token' --- implicit +-- Test grant_type = 'token' [GET] +-- implicit [deprecated] http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback -- Test from 'code' get 'token' [POST] -http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http://localhost:8080/os/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 - - +http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback + +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test' \ +--data-urlencode 'client_secret=test' \ +--data-urlencode 'grant_type=authorization_code' \ +--data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \ +--data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7' + +response +{ + "access_token": "fab3c3a571432376d427dc402ef904ad", + "refresh_token": "8c46797a0101800626270ce6579c84fa", + "token_type": "Bearer", + "expires_in": 43199 +} -- Test grant_type='password' [POST] http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=password&scope=read write&username=test&password=test +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test' \ +--data-urlencode 'client_secret=test' \ +--data-urlencode 'grant_type=password' \ +--data-urlencode 'username=test' \ +--data-urlencode 'password=test' \ +--data-urlencode 'scope=read' + +response +{ + "access_token": "4bb0e34d3e517f53f0f7f3e9cb1f2c1e", + "token_type": "Bearer", + "expires_in": 16342 +} -- Test grant_type='client_credentials' [POST] http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=client_credentials&scope=read +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test' \ +--data-urlencode 'client_secret=test' \ +--data-urlencode 'grant_type=client_credentials' \ +--data-urlencode 'scope=read' + +response +{ + "access_token": "4bb0e34d3e517f53f0f7f3e9cb1f2c1e", + "token_type": "Bearer", + "expires_in": 16169 +} -- Test grant_type='refresh_token' [POST] http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 - +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=test' \ +--data-urlencode 'client_secret=test' \ +--data-urlencode 'grant_type=refresh_token' \ +--data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa' + +response +{ + "access_token": "d64b33d5ed08e7a17d9e9e6a723b432f", + "refresh_token": "9c6d694f02d1cb472b39c91644d52f5d", + "token_type": "Bearer", + "expires_in": 43199 +} -- Test-Page URL: http://localhost:8080/html/oauth_test.html @@ -46,7 +101,7 @@ Test-Page URL: http://localhost:8080/html/oauth_test.html -- Test resource -http://localhost:8080/rs/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4c4 +http://localhost:8083/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4c4 -- @@ -54,11 +109,11 @@ http://localhost:8080/rs/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4 -- -- Test grant_type='password' [POST] -http://localhost:8080/authz/oauth/token?client_id=mobile&client_secret=mobile&grant_type=password&scope=read write&username=test&password=test +http://localhost:8080/oauth/token?client_id=mobile&client_secret=mobile&grant_type=password&scope=read write&username=test&password=test -- Test mobile resource -http://qc8.cc:8080/rs/mobile/system_time?access_token=b69c2f61212780c901e69cebd6854667 +http://localhost:8080/mobile/system_time?access_token=b69c2f61212780c901e69cebd6854667 -- Gitee From e8b398d0be1ba2da108a27206b27db5a7aa75714 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 18:46:09 +0800 Subject: [PATCH 39/78] oauth test --- others/oauth_test.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/others/oauth_test.txt b/others/oauth_test.txt index cb7c35a..471fdda 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -111,9 +111,25 @@ http://localhost:8083/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4c4 -- Test grant_type='password' [POST] http://localhost:8080/oauth/token?client_id=mobile&client_secret=mobile&grant_type=password&scope=read write&username=test&password=test +curl --location 'http://localhost:8080/oauth/token' \ +--header 'Content-Type: application/x-www-form-urlencoded' \ +--data-urlencode 'client_id=mobile' \ +--data-urlencode 'client_secret=mobile' \ +--data-urlencode 'grant_type=password' \ +--data-urlencode 'username=test' \ +--data-urlencode 'password=test' \ +--data-urlencode 'scope=read' +response +{ + "access_token": "ded9cdf300b8d950fa8b61a6844514aa", + "refresh_token": "f264790c4b1140ff6dfdc40b5fbd7410", + "token_type": "Bearer", + "expires_in": 43199 +} + -- Test mobile resource -http://localhost:8080/mobile/system_time?access_token=b69c2f61212780c901e69cebd6854667 +http://localhost:8083/mobile/system_time?access_token=b69c2f61212780c901e69cebd6854667 -- Gitee From 9a2f7b7d107895bf22ce5fd3c5ea0ea16ce5a9ba Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 21 Sep 2023 22:31:50 +0800 Subject: [PATCH 40/78] oauth test --- .../src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java index accafa9..c586052 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java @@ -70,6 +70,7 @@ public class OAuth2Filter extends AuthenticatingFilter implements InitializingBe } + @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; } -- Gitee From 7708b28a962edf952d0ac324a54b2ff23014bb42 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 15:29:18 +0800 Subject: [PATCH 41/78] oauth test, resource --- .../main/java/com/monkeyk/os/config/RsSecurityConfig.java | 8 ++++---- .../monkeyk/os/oauth/resources/ResourceOAuthFilter.java | 2 +- .../java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java | 1 + .../java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java index afdc85a..047ba2e 100644 --- a/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java +++ b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java @@ -94,8 +94,8 @@ public class RsSecurityConfig { *

    * resourceId 在新的OAuth2实现中是可选的了 @since 2.0.0 */ - @Bean - public OAuth2Filter auth2Filter() { +// @Bean + OAuth2Filter auth2Filter() { OAuth2Filter oAuth2Filter = new OAuth2Filter(); oAuth2Filter.setResourceId("os-resource"); oAuth2Filter.setRsService(oAuthRSService); @@ -105,8 +105,8 @@ public class RsSecurityConfig { /** * mobile resource */ - @Bean - public OAuth2Filter mobileOauth2Filter() { +// @Bean + OAuth2Filter mobileOauth2Filter() { OAuth2Filter oAuth2Filter = new OAuth2Filter(); oAuth2Filter.setResourceId("mobile-resource"); oAuth2Filter.setRsService(oAuthRSService); diff --git a/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java b/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java index 65c8550..5b99918 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java @@ -137,7 +137,7 @@ public class ResourceOAuthFilter implements Filter { //nothing } - /* + /** * 默认的 ERROR 返回信息在header中, * 可根据实际需要修改为JSON或XML数据格式返回 */ diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java index c586052..2f5e047 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java @@ -28,6 +28,7 @@ import javax.servlet.http.HttpServletResponse; * 相关配置见 RsSecurityConfig.java * * @author Shengzhao Li + * @see com.monkeyk.os.oauth.resources.ResourceOAuthFilter */ public class OAuth2Filter extends AuthenticatingFilter implements InitializingBean { diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java index 3b26c9b..70bd88a 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2JdbcRealm.java @@ -135,6 +135,6 @@ public class OAuth2JdbcRealm extends MkkJdbcRealm { public void afterPropertiesSet() throws Exception { super.afterPropertiesSet(); - Assert.notNull(this.rsService); + Assert.notNull(this.rsService, "rsService is required"); } } -- Gitee From f8345fe622e069057dcde4aae235ab182137c03b Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 15:43:40 +0800 Subject: [PATCH 42/78] oauth test, resource --- .../monkeyk/os/oauth/shiro/OAuth2Token.java | 12 ++++++--- .../monkeyk/os/config/RsSecurityConfig.java | 4 +-- .../oauth/resources/ResourceOAuthFilter.java | 5 ++-- .../monkeyk/os/oauth/shiro/OAuth2Filter.java | 27 +++++++++---------- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java b/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java index 106daa6..f72b9b9 100644 --- a/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java +++ b/core/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Token.java @@ -13,13 +13,19 @@ public class OAuth2Token implements RememberMeAuthenticationToken { private static final long serialVersionUID = 8587854556973099598L; - // the service access_token + /** + * the service access_token + */ private String accessToken; - // the user identifier, username + /** + * the user identifier, username + */ private String userId; - // is the user in a remember me mode ? + /** + * is the user in a remember me mode ? + */ private boolean rememberMe = false; diff --git a/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java index 047ba2e..80fcb2d 100644 --- a/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java +++ b/resources/src/main/java/com/monkeyk/os/config/RsSecurityConfig.java @@ -93,8 +93,8 @@ public class RsSecurityConfig { * Single resource *

    * resourceId 在新的OAuth2实现中是可选的了 @since 2.0.0 + * 注意:不配置为bean */ -// @Bean OAuth2Filter auth2Filter() { OAuth2Filter oAuth2Filter = new OAuth2Filter(); oAuth2Filter.setResourceId("os-resource"); @@ -104,8 +104,8 @@ public class RsSecurityConfig { /** * mobile resource + * 注意:不配置为bean */ -// @Bean OAuth2Filter mobileOauth2Filter() { OAuth2Filter oAuth2Filter = new OAuth2Filter(); oAuth2Filter.setResourceId("mobile-resource"); diff --git a/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java b/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java index 5b99918..a77359c 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/resources/ResourceOAuthFilter.java @@ -11,6 +11,7 @@ import org.apache.oltu.oauth2.rs.response.OAuthRSResponse; import org.apache.oltu.oauth2.rsfilter.OAuthDecision; import org.apache.oltu.oauth2.rsfilter.OAuthRSProvider; import org.apache.oltu.oauth2.rsfilter.OAuthUtils; +import org.apache.shiro.web.util.WebUtils; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; @@ -90,8 +91,8 @@ public class ResourceOAuthFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse res = (HttpServletResponse) response; + HttpServletRequest req = WebUtils.toHttp(request); + HttpServletResponse res = WebUtils.toHttp(response); try { diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java index 2f5e047..d9c7ae1 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java @@ -9,7 +9,9 @@ import org.apache.oltu.oauth2.common.message.OAuthResponse; import org.apache.oltu.oauth2.rs.response.OAuthRSResponse; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.web.filter.authc.AuthenticatingFilter; +import org.apache.shiro.authc.BearerToken; +import org.apache.shiro.web.filter.authc.BearerHttpAuthenticationFilter; +import org.apache.shiro.web.util.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -30,22 +32,26 @@ import javax.servlet.http.HttpServletResponse; * @author Shengzhao Li * @see com.monkeyk.os.oauth.resources.ResourceOAuthFilter */ -public class OAuth2Filter extends AuthenticatingFilter implements InitializingBean { +public class OAuth2Filter extends BearerHttpAuthenticationFilter implements InitializingBean { private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Filter.class); - private String resourceId; + /** + * resourceId 是可选的,可忽略, since 2.0.0 + */ + private String resourceId = "default"; private OAuthRSService rsService; @Override - protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { + protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { - HttpServletRequest httpRequest = (HttpServletRequest) request; + HttpServletRequest httpRequest = WebUtils.toHttp(request); - final String accessToken = getAccessToken(httpRequest); + BearerToken bearerToken = (BearerToken) super.createToken(request, response); + final String accessToken = bearerToken.getToken(); final AccessToken token = rsService.loadAccessTokenByTokenId(accessToken); String username = null; @@ -61,15 +67,6 @@ public class OAuth2Filter extends AuthenticatingFilter implements InitializingBe .setUserId(username); } - private String getAccessToken(HttpServletRequest httpRequest) { - final String authorization = httpRequest.getHeader("Authorization"); - if (authorization != null) { - //bearer ab1ade69-d122-4844-ab23-7b109ad977f0 - return authorization.substring(6).trim(); - } - return httpRequest.getParameter(OAuth.OAUTH_ACCESS_TOKEN); - } - @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { -- Gitee From 120f24c3f8d7a560bae0a4676b02f0eabb3f0269 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 15:45:09 +0800 Subject: [PATCH 43/78] oauth test, resource --- .../main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java index d9c7ae1..442af6d 100644 --- a/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java +++ b/resources/src/main/java/com/monkeyk/os/oauth/shiro/OAuth2Filter.java @@ -112,5 +112,9 @@ public class OAuth2Filter extends BearerHttpAuthenticationFilter implements Init public void afterPropertiesSet() throws Exception { Assert.notNull(resourceId, "resourceId is null"); Assert.notNull(rsService, "rsService is null"); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Initialized: {} -> resourceId: {}, rsService: {}", this, resourceId, rsService); + } } } -- Gitee From 5e5ec1a4d423d5d72a2540a1b940922481d3c8d3 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 15:51:40 +0800 Subject: [PATCH 44/78] oauth test, resource --- others/oauth_test.txt | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/others/oauth_test.txt b/others/oauth_test.txt index 471fdda..bcb9cb2 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -101,7 +101,16 @@ Test-Page URL: http://localhost:8080/html/oauth_test.html -- Test resource -http://localhost:8083/rs/username?access_token=6b7b0726e603cd04c797d5b28c7be4c4 +http://localhost:8083/rs/username [GET] + +curl --location 'http://localhost:8083/rs/username' \ +--header 'Authorization: Bearer d64b33d5ed08e7a17d9e9e6a723b432f' + +response +{ + "clientId": "test-client", + "username": "test" +} -- @@ -130,6 +139,13 @@ response } -- Test mobile resource -http://localhost:8083/mobile/system_time?access_token=b69c2f61212780c901e69cebd6854667 +http://localhost:8083/mobile/system_time [GET] +curl --location 'http://localhost:8083/mobile/system_time' \ +--header 'Authorization: Bearer ded9cdf300b8d950fa8b61a6844514aa' + +response +{ + "time": 1695628213913 +} -- Gitee From 11a3eb36560ebddd549290a926b5dce4d2198a5b Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 15:56:34 +0800 Subject: [PATCH 45/78] oauth test, resource --- authz/src/main/resources/static/html/oauth_test.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authz/src/main/resources/static/html/oauth_test.html b/authz/src/main/resources/static/html/oauth_test.html index f89763c..f48483f 100644 --- a/authz/src/main/resources/static/html/oauth_test.html +++ b/authz/src/main/resources/static/html/oauth_test.html @@ -242,7 +242,7 @@


    - © oauth2-shiro + © 2015-2023 oauth2-shiro
  • -- Gitee From 7a5a528830357ce70609780e311c6ab1e29e314e Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:09:37 +0800 Subject: [PATCH 46/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../os/config/AuthzSecurityConfig.java | 19 +++++- .../os/domain/users/PasswordHandler.java | 10 +-- .../users/password/MD5PasswordEncoder.java | 27 ++++++++ .../users/password/PasswordEncoder.java | 32 +++++++++ .../users/password/ShaPasswordEncoder.java | 54 +++++++++++++++ .../src/main/resources/application.properties | 4 ++ .../password/MD5PasswordEncoderTest.java | 44 +++++++++++++ .../password/ShaPasswordEncoderTest.java | 66 +++++++++++++++++++ 8 files changed, 247 insertions(+), 9 deletions(-) create mode 100644 authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java create mode 100644 authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java create mode 100644 authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java create mode 100644 authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java create mode 100644 authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java index d074368..af69e3d 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -5,11 +5,13 @@ import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.AbstractCacheManager; import org.apache.shiro.cache.MemoryConstrainedCacheManager; +import org.apache.shiro.crypto.hash.Sha256Hash; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,14 +36,27 @@ public class AuthzSecurityConfig { * * */ + /** + * 存储凭证(如密码)的加密算法(如 MD5,SHA-256,SHA-384,SHA-512) + * 默认 SHA-256 + *

    + * 若是旧版本升级后继续使用MD5,请将配置值更新为MD5 + * + * authz.store.credentials.alg=MD5 + * + * + * @since 2.0.0 + */ + @Value("authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME) + private String storeCredentialsAlg; /** - * 使用MD5 进行密码的加密 + * 使用指定算法 进行密码的加密与匹配 */ @Bean public CredentialsMatcher credentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); - credentialsMatcher.setHashAlgorithmName("MD5"); + credentialsMatcher.setHashAlgorithmName(this.storeCredentialsAlg); // credentialsMatcher.setStoredCredentialsHexEncoded(false); return credentialsMatcher; } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java b/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java index d316eaa..a6fe57e 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java @@ -3,6 +3,7 @@ package com.monkeyk.os.domain.users; import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -30,12 +31,7 @@ public abstract class PasswordHandler { throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK)."); } - try { - byte[] bytes = digest.digest(password.getBytes("UTF-8")); - return String.format("%032x", new BigInteger(1, bytes)); - } catch (UnsupportedEncodingException e) { - throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK)."); - } - + byte[] bytes = digest.digest(password.getBytes(StandardCharsets.UTF_8)); + return String.format("%032x", new BigInteger(1, bytes)); } } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java new file mode 100644 index 0000000..866956d --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java @@ -0,0 +1,27 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.crypto.hash.Md5Hash; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * 2023/9/25 16:23 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public class MD5PasswordEncoder implements PasswordEncoder { + + @Override + public String encode(CharSequence rawPassword) { + Md5Hash md5Hash = new Md5Hash(rawPassword); + return md5Hash.toHex(); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + String encode = this.encode(rawPassword); + return MessageDigest.isEqual(encode.getBytes(StandardCharsets.UTF_8), encodedPassword.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java new file mode 100644 index 0000000..402f43a --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java @@ -0,0 +1,32 @@ +package com.monkeyk.os.domain.users.password; + +/** + * 2023/9/25 16:19 + *

    + * 密码加密 与 验证 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public interface PasswordEncoder { + + + /** + * 加密密码 + * + * @param rawPassword 原始密码 + * @return 加密后的密码 + */ + String encode(CharSequence rawPassword); + + + /** + * 校验加密的密码是否正确 + * + * @param rawPassword 原始密码 + * @param encodedPassword 加密的密码 + * @return true 校验正确,其他 false + */ + boolean matches(CharSequence rawPassword, String encodedPassword); + +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java new file mode 100644 index 0000000..887da19 --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java @@ -0,0 +1,54 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.crypto.hash.SimpleHash; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * 2023/9/25 16:47 + *

    + * SHA-xx 算法实现抽象, + * 如SHA-256 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +public class ShaPasswordEncoder implements PasswordEncoder { + + private static final String SHA_PREFIX = "SHA-"; + + /** + * 如 256, 384, 512 + */ + private int length; + + /** + * Alg + */ + private final String alg; + + /** + * SHA算法 + * + * @param length 如 256, 384, 512 + */ + public ShaPasswordEncoder(int length) { + this.alg = SHA_PREFIX + length; + + } + + @Override + public String encode(CharSequence rawPassword) { + SimpleHash simpleHash = new SimpleHash(this.alg, rawPassword); + return simpleHash.toHex(); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + String encode = this.encode(rawPassword); + return MessageDigest.isEqual( + encode.getBytes(StandardCharsets.UTF_8), + encodedPassword.getBytes(StandardCharsets.UTF_8)); + } +} diff --git a/authz/src/main/resources/application.properties b/authz/src/main/resources/application.properties index 3084f84..e81923b 100644 --- a/authz/src/main/resources/application.properties +++ b/authz/src/main/resources/application.properties @@ -26,3 +26,7 @@ spring.thymeleaf.cache=false # server.port=8080 # +# security upgrade config property +# +#authz.store.credentials.alg=SHA-256 +# \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java new file mode 100644 index 0000000..f5b5fec --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java @@ -0,0 +1,44 @@ +package com.monkeyk.os.domain.users.password; + +import org.apache.shiro.crypto.hash.Md5Hash; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 16:28 + * + * @author Shengzhao Li + */ +class MD5PasswordEncoderTest { + + @Test + void encode() { + + MD5PasswordEncoder passwordEncoder = new MD5PasswordEncoder(); + String rawPwd = "Admin@2023&"; + String encode = passwordEncoder.encode(rawPwd); + + assertNotNull(encode); +// System.out.println(encode); + assertEquals("eb733df7498c0e973c5c7bdfecd1016c", encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode); + assertTrue(matches); + + } + + + @Test + void encodeHex() { + + Md5Hash md5Hash = new Md5Hash("admin"); + String hex = md5Hash.toHex(); + assertNotNull(hex); + +// System.out.println(md5Hash.toBase64()); + assertEquals("21232f297a57a5a743894a0e4a801fc3", hex); + + } + +} \ No newline at end of file diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java new file mode 100644 index 0000000..c6dc965 --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java @@ -0,0 +1,66 @@ +package com.monkeyk.os.domain.users.password; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 17:04 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +class ShaPasswordEncoderTest { + + + @Test + void encode256() { + + String rawPwd = "Admin@2023&"; + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); + String encode = passwordEncoder.encode(rawPwd); + + assertNotNull(encode); + assertEquals("cf41c9f84f0b08aa184f9031e402602a69474ca6d6e50964c58250f1938eb8f3", encode); +// System.out.println(encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode); + assertTrue(matches); + + } + + @Test + void encode384() { + + String rawPwd = "Admin@2023&"; + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(384); + String encode = passwordEncoder.encode(rawPwd); + + assertNotNull(encode); + assertEquals("0a32b1b88f37ad21ac65aae125cec7814e5535d1077a4a7e6ccbf48a16800603e04c6909d9a19c68080a248b7ca11034", encode); +// System.out.println(encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode); + assertTrue(matches); + + } + + @Test + void encode512() { + + String rawPwd = "Admin@2023&"; + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(512); + String encode = passwordEncoder.encode(rawPwd); + + assertNotNull(encode); + assertEquals( + "21dc4b7e4c58ece1d581644231d19d3a09ab5e1047222f28835a05efc3d9293b3d4df18654d0fd985aad592f154cd7fcefab513997ccc1cf9833f362590e63b8", + encode); +// System.out.println(encode); + + boolean matches = passwordEncoder.matches(rawPwd, encode); + assertTrue(matches); + + } + +} \ No newline at end of file -- Gitee From 87226ffca9da958755cb45fba94bd46913eaace2 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:46:35 +0800 Subject: [PATCH 47/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../os/domain/users/AuthzPasswordEncoder.java | 102 ++++++++++++++++++ .../os/domain/users/PasswordHandler.java | 1 + .../users/password/ShaPasswordEncoder.java | 10 ++ .../os/service/business/UsersFormSaver.java | 20 ++-- .../monkeyk/os/service/dto/UsersFormDto.java | 3 +- .../os/service/impl/UserServiceImpl.java | 6 +- authz/src/main/resources/templates/login.html | 2 +- .../templates/oauth/oauth_login.html | 2 +- .../users/AuthzPasswordEncoderTest.java | 38 +++++++ others/database/initial-db.ddl | 6 +- 10 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java create mode 100644 authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java new file mode 100644 index 0000000..aefedec --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java @@ -0,0 +1,102 @@ +package com.monkeyk.os.domain.users; + +import com.monkeyk.os.domain.users.password.MD5PasswordEncoder; +import com.monkeyk.os.domain.users.password.PasswordEncoder; +import com.monkeyk.os.domain.users.password.ShaPasswordEncoder; +import org.apache.shiro.crypto.hash.*; +import org.apache.shiro.util.Assert; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 2023/9/25 17:15 + *

    + * 对各类密码的 加密,校验封装 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class AuthzPasswordEncoder implements InitializingBean { + + private static final Logger LOG = LoggerFactory.getLogger(AuthzPasswordEncoder.class); + + /** + * 存储凭证(如密码)的加密算法(如 MD5,SHA-256,SHA-384,SHA-512) + * 默认 SHA-256 + *

    + * 若是旧版本升级后继续使用MD5,请将配置值更新为MD5 + * + * authz.store.credentials.alg=MD5 + * + * + * @since 2.0.0 + */ + @Value("authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME) + private String storeCredentialsAlg; + + + private PasswordEncoder passwordEncoder; + + + public AuthzPasswordEncoder() { + } + + + /** + * 加密 + * + * @param rasPassword 原始密码 + * @return 加密后的 + */ + public String encode(String rasPassword) { + return this.passwordEncoder.encode(rasPassword); + } + + /** + * 校验密码是否正确 + * + * @param rawPassword 原始密码 + * @param encodedPassword 加密的密码 + * @return true 校验正确,其他 false + */ + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return this.passwordEncoder.matches(rawPassword, encodedPassword); + } + + + @Override + public void afterPropertiesSet() throws Exception { + Assert.notNull(this.storeCredentialsAlg, "storeCredentialsAlg is null"); + + switch (this.storeCredentialsAlg) { + case Sha256Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(256); + break; + case Sha384Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(384); + break; + case Sha512Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(512); + break; + case Sha1Hash.ALGORITHM_NAME: + this.passwordEncoder = new ShaPasswordEncoder(1); + if (LOG.isWarnEnabled()) { + LOG.warn("[Deprecated] alg: {} is unsafe, not recommended for use (try use SHA-256 or SHA-512 much better)", Sha1Hash.ALGORITHM_NAME); + } + break; + case Md5Hash.ALGORITHM_NAME: + this.passwordEncoder = new MD5PasswordEncoder(); + if (LOG.isWarnEnabled()) { + LOG.warn("[Deprecated] alg: {} is unsafe, not recommended for use (try use SHA-256 or SHA-512 much better)", Md5Hash.ALGORITHM_NAME); + } + break; + default: + throw new IllegalArgumentException("Unsupport storeCredentialsAlg: " + + this.storeCredentialsAlg + ", please checking property 'authz.store.credentials.alg' "); + } + } +} diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java b/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java index a6fe57e..95867ba 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/PasswordHandler.java @@ -14,6 +14,7 @@ import java.security.NoSuchAlgorithmException; * 使用MD5 * * @author Shengzhao Li + * @deprecated use AuthzPasswordEncoder.java replaced from 2.0.0 */ public abstract class PasswordHandler { diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java index 887da19..7466ab8 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java @@ -38,6 +38,16 @@ public class ShaPasswordEncoder implements PasswordEncoder { } + + /** + * SHA算法 + * + * @param alg 如 SHA-256, SHA-384, SHA-512 + */ + public ShaPasswordEncoder(String alg) { + this.alg = alg; + } + @Override public String encode(CharSequence rawPassword) { SimpleHash simpleHash = new SimpleHash(this.alg, rawPassword); diff --git a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java index 83e6a7a..a226fe0 100644 --- a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java +++ b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java @@ -1,10 +1,12 @@ package com.monkeyk.os.service.business; -import com.monkeyk.os.domain.shared.BeanProvider; +import com.monkeyk.os.domain.users.AuthzPasswordEncoder; import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; import com.monkeyk.os.domain.users.UsersAuthzRepository; import com.monkeyk.os.service.dto.UsersFormDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; import java.util.List; @@ -13,20 +15,26 @@ import java.util.List; * * @author Shengzhao Li */ +@Component public class UsersFormSaver { - private transient UsersAuthzRepository usersRepository = BeanProvider.getBean(UsersAuthzRepository.class); + @Autowired + private UsersAuthzRepository usersRepository; + @Autowired + private AuthzPasswordEncoder passwordEncoder; - private UsersFormDto formDto; - public UsersFormSaver(UsersFormDto formDto) { - this.formDto = formDto; + public UsersFormSaver() { } - public String save() { + + public String save(UsersFormDto formDto) { Users users = formDto.newUsers(); + //用指定的加密方式 + users.password(this.passwordEncoder.encode(formDto.getPassword())); + final int id = usersRepository.saveUsers(users); final List roleGuids = formDto.getRoleGuids(); diff --git a/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java b/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java index 092acaf..c77d58d 100644 --- a/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java +++ b/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java @@ -1,7 +1,6 @@ package com.monkeyk.os.service.dto; import com.monkeyk.os.domain.shared.GuidGenerator; -import com.monkeyk.os.domain.users.PasswordHandler; import com.monkeyk.os.domain.users.Users; import java.util.ArrayList; @@ -39,7 +38,7 @@ public class UsersFormDto extends UsersDto { public Users newUsers() { return new Users() .username(getUsername()) - .password(PasswordHandler.md5(getPassword())) +// .password(PasswordHandler.md5(getPassword())) .guid(GuidGenerator.generate()); } } diff --git a/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java b/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java index 99e865a..23138dd 100644 --- a/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java +++ b/authz/src/main/java/com/monkeyk/os/service/impl/UserServiceImpl.java @@ -24,6 +24,9 @@ public class UserServiceImpl implements UserService { @Autowired private UsersAuthzRepository usersAuthzRepository; + @Autowired + private UsersFormSaver usersFormSaver; + @Override public UsersOverviewDto loadUsersOverviewDto(String username) { List usersList = usersAuthzRepository.findUsersByUsername(username); @@ -44,7 +47,6 @@ public class UserServiceImpl implements UserService { @Override public String saveUsers(UsersFormDto formDto) { - UsersFormSaver saver = new UsersFormSaver(formDto); - return saver.save(); + return this.usersFormSaver.save(formDto); } } diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html index bf1e2e8..5ccbbd2 100644 --- a/authz/src/main/resources/templates/login.html +++ b/authz/src/main/resources/templates/login.html @@ -40,7 +40,7 @@

    (test) + placeholder="Type password" id="password" th:field="*{password}"/> (Test@2015#)
    diff --git a/authz/src/main/resources/templates/oauth/oauth_login.html b/authz/src/main/resources/templates/oauth/oauth_login.html index 9affc4d..85c85da 100644 --- a/authz/src/main/resources/templates/oauth/oauth_login.html +++ b/authz/src/main/resources/templates/oauth/oauth_login.html @@ -75,7 +75,7 @@ test - test + Test@2015# authorization_code,password,refresh_token,client_credentials User(id=22) diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java new file mode 100644 index 0000000..baf0e7f --- /dev/null +++ b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java @@ -0,0 +1,38 @@ +package com.monkeyk.os.domain.users; + +import com.monkeyk.os.domain.users.password.ShaPasswordEncoder; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 17:38 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +class AuthzPasswordEncoderTest { + + + @Test + void initPwd() { + + //默认 SHA-256 + ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); + + String adminPwd = "Admin@2015#"; + String encode = passwordEncoder.encode(adminPwd); + assertNotNull(encode); + assertEquals("9e44eb14dc2f051343b0dc92d935ff4ae49aa626a8fc08964172c5d18f3fc3a9", encode); +// System.out.println(encode); + + String testPwd = "Test@2015#"; + String encode2 = passwordEncoder.encode(testPwd); + assertNotNull(encode2); + assertEquals("88d67a920783fd411b30943c3310a25f605e40406d24cf6f17bc6b20a379ecd6", encode2); +// System.out.println(encode2); + + + } + +} \ No newline at end of file diff --git a/others/database/initial-db.ddl b/others/database/initial-db.ddl index d3d2159..5accb5c 100644 --- a/others/database/initial-db.ddl +++ b/others/database/initial-db.ddl @@ -4,12 +4,12 @@ set names utf8; --- Two users: admin/admin; test/test +-- Two users: admin/Admin@2015#; test/Test@2015# truncate users; insert into users(id,guid,create_time,username,password,default_user) values -(21,uuid(),now(),'admin','21232f297a57a5a743894a0e4a801fc3',1), -(22,uuid(),now(),'test','098f6bcd4621d373cade4e832627b4f6',0); +(21,uuid(),now(),'admin','9e44eb14dc2f051343b0dc92d935ff4ae49aa626a8fc08964172c5d18f3fc3a9',1), +(22,uuid(),now(),'test','88d67a920783fd411b30943c3310a25f605e40406d24cf6f17bc6b20a379ecd6',0); -- Two roles: User,Admin -- Gitee From 5ee40e29a58a0192f19ecee167a579ce81cf97e6 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:49:08 +0800 Subject: [PATCH 48/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256,?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/monkeyk/os/config/AuthzSecurityConfig.java | 2 +- .../java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java index af69e3d..5000793 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzSecurityConfig.java @@ -47,7 +47,7 @@ public class AuthzSecurityConfig { * * @since 2.0.0 */ - @Value("authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME) + @Value("${authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME + "}") private String storeCredentialsAlg; /** diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java index aefedec..3426c9b 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java @@ -35,7 +35,7 @@ public class AuthzPasswordEncoder implements InitializingBean { * * @since 2.0.0 */ - @Value("authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME) + @Value("${authz.store.credentials.alg:" + Sha256Hash.ALGORITHM_NAME + "}") private String storeCredentialsAlg; -- Gitee From 93432804853e63c420cb0b4ce5934a0fcbb93633 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:51:19 +0800 Subject: [PATCH 49/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256,=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- authz/src/main/resources/templates/login.html | 2 +- authz/src/main/resources/templates/oauth/oauth_login.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html index 5ccbbd2..b7200a6 100644 --- a/authz/src/main/resources/templates/login.html +++ b/authz/src/main/resources/templates/login.html @@ -46,7 +46,7 @@
      - +
    diff --git a/authz/src/main/resources/templates/oauth/oauth_login.html b/authz/src/main/resources/templates/oauth/oauth_login.html index 85c85da..a4a875b 100644 --- a/authz/src/main/resources/templates/oauth/oauth_login.html +++ b/authz/src/main/resources/templates/oauth/oauth_login.html @@ -51,7 +51,7 @@
      - Login failed + Login failed
    -- Gitee From d74a14df10cfd51a9503de4596c7c2584ec39781 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:56:28 +0800 Subject: [PATCH 50/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256,=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- authz/src/main/resources/templates/users/user_plus.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/authz/src/main/resources/templates/users/user_plus.html b/authz/src/main/resources/templates/users/user_plus.html index 0e89150..6a5c120 100644 --- a/authz/src/main/resources/templates/users/user_plus.html +++ b/authz/src/main/resources/templates/users/user_plus.html @@ -31,10 +31,10 @@
    - + -

    Password, required.

    +

    Password should include upper and lower case letters, numbers, and special characters, with a minimum length of 10 characters, required.

    -- Gitee From d8e549c64e71db49f65a4a713c736fbb0be2db52 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:57:43 +0800 Subject: [PATCH 51/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256,=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- authz/src/main/resources/templates/users/user_plus.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authz/src/main/resources/templates/users/user_plus.html b/authz/src/main/resources/templates/users/user_plus.html index 6a5c120..21661a8 100644 --- a/authz/src/main/resources/templates/users/user_plus.html +++ b/authz/src/main/resources/templates/users/user_plus.html @@ -21,7 +21,7 @@
    -

    Username, unique.

    @@ -31,7 +31,7 @@
    -

    Password should include upper and lower case letters, numbers, and special characters, with a minimum length of 10 characters, required.

    -- Gitee From 996e1c11ff8c210902b091ff7578d776f3e27618 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 17:58:44 +0800 Subject: [PATCH 52/78] =?UTF-8?q?password=20=E7=AE=97=E6=B3=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4=E7=94=A8?= =?UTF-8?q?=20SHA-256,=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- authz/src/main/resources/templates/users/user_plus.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/authz/src/main/resources/templates/users/user_plus.html b/authz/src/main/resources/templates/users/user_plus.html index 21661a8..f3d9369 100644 --- a/authz/src/main/resources/templates/users/user_plus.html +++ b/authz/src/main/resources/templates/users/user_plus.html @@ -22,7 +22,7 @@
    + required="required" autocomplete="off" minlength="5"/>

    Username, unique.

    @@ -32,7 +32,7 @@
    + required="required" minlength="10" autocomplete="off"/>

    Password should include upper and lower case letters, numbers, and special characters, with a minimum length of 10 characters, required.

    -- Gitee From 8c247932b6b563728b22d70cd079adc309100689 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 18:07:52 +0800 Subject: [PATCH 53/78] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E4=B8=8E=20=E6=B5=8B=E8=AF=95=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=AF=86=E7=A0=81=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- others/database/initial-db.ddl | 4 ++-- others/oauth_test.txt | 40 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/others/database/initial-db.ddl b/others/database/initial-db.ddl index 5accb5c..2a69332 100644 --- a/others/database/initial-db.ddl +++ b/others/database/initial-db.ddl @@ -48,7 +48,7 @@ insert into oauth_client_details(client_id, client_secret, client_name, client_u client_icon_uri, resource_ids, scope, grant_types, redirect_uri, roles) values -('test','test','Test Client','http://andaily.com', +('test-client','Test@2015$$','Test Client','http://andaily.com', 'http://andaily.com/favicon.ico','os-resource','read write','authorization_code,password,refresh_token,client_credentials', 'http://localhost:7777/spring-oauth-client/authorization_code_callback','22'); -- Mobile resource client details @@ -56,7 +56,7 @@ insert into oauth_client_details(client_id, client_secret, client_name, client_u client_icon_uri, resource_ids, scope, grant_types, redirect_uri, roles) values -('mobile','mobile','Mobile Client','http://andaily.com', +('mobile-client','Mobile@2015$$','Mobile Client','http://andaily.com', 'http://andaily.com/favicon.ico','mobile-resource','read write','password,refresh_token', 'http://localhost:7777/spring-oauth-client/authorization_code_callback','22'); diff --git a/others/oauth_test.txt b/others/oauth_test.txt index bcb9cb2..d5ab5ae 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -6,24 +6,24 @@ resources -> port: 8083 -- Test grant_type = 'authorization_code' , get code [GET] -http://localhost:8080/oauth/authorize?response_type=code&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 +http://localhost:8080/oauth/authorize?response_type=code&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=09876999 -http://localhost:8080/oauth/authorize?response_type=code&scope=read%20write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 +http://localhost:8080/oauth/authorize?response_type=code&scope=read%20write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback&state=swss58522555 -- Test grant_type = 'token' [GET] -- implicit [deprecated] -http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback +http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback -- Test from 'code' get 'token' [POST] -http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback curl --location 'http://localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'client_id=test' \ ---data-urlencode 'client_secret=test' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \ --data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7' @@ -37,15 +37,15 @@ response } -- Test grant_type='password' [POST] -http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=password&scope=read write&username=test&password=test +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=password&scope=read write&username=test&password=Test@2015# curl --location 'http://localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'client_id=test' \ ---data-urlencode 'client_secret=test' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'username=test' \ ---data-urlencode 'password=test' \ +--data-urlencode 'password=Test@2015#' \ --data-urlencode 'scope=read' response @@ -57,12 +57,12 @@ response -- Test grant_type='client_credentials' [POST] -http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=client_credentials&scope=read +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=client_credentials&scope=read curl --location 'http://localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'client_id=test' \ ---data-urlencode 'client_secret=test' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ --data-urlencode 'grant_type=client_credentials' \ --data-urlencode 'scope=read' @@ -74,12 +74,12 @@ response } -- Test grant_type='refresh_token' [POST] -http://localhost:8080/oauth/token?client_id=test&client_secret=test&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 +http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1 curl --location 'http://localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'client_id=test' \ ---data-urlencode 'client_secret=test' \ +--data-urlencode 'client_id=test-client' \ +--data-urlencode 'client_secret=Test@2015$$' \ --data-urlencode 'grant_type=refresh_token' \ --data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa' @@ -118,15 +118,15 @@ response -- -- Test grant_type='password' [POST] -http://localhost:8080/oauth/token?client_id=mobile&client_secret=mobile&grant_type=password&scope=read write&username=test&password=test +http://localhost:8080/oauth/token?client_id=mobile-client&client_secret=Mobile@2015$$&grant_type=password&scope=read write&username=test&password=Test@2015# curl --location 'http://localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'client_id=mobile' \ ---data-urlencode 'client_secret=mobile' \ +--data-urlencode 'client_id=mobile-client' \ +--data-urlencode 'client_secret=Mobile@2015$$' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'username=test' \ ---data-urlencode 'password=test' \ +--data-urlencode 'password=Test@2015#' \ --data-urlencode 'scope=read' -- Gitee From 14403f38239b0da2a5b0272e085b94dd2bb00236 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 18:09:46 +0800 Subject: [PATCH 54/78] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E4=B8=8E=20=E6=B5=8B=E8=AF=95=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E5=AF=86=E7=A0=81=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/monkeyk/os/domain/oauth/ClientDetails.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java b/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java index 6b92dfd..0b20da9 100644 --- a/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java +++ b/core/src/main/java/com/monkeyk/os/domain/oauth/ClientDetails.java @@ -22,6 +22,8 @@ public class ClientDetails extends BasicClientInfo { /** * 客户端所拥有的资源ID(resource-id), 至少有一个, * 多个ID时使用逗号(,)分隔, 如: os,mobile + *

    + * 可选,可使用一个指定的 resourceIds */ private String resourceIds; @@ -33,9 +35,9 @@ public class ClientDetails extends BasicClientInfo { */ private String grantTypes; - /* - * Shiro roles - * */ + /** + * Shiro roles + * */ private String roles; /** -- Gitee From bd455f8021344c2f1dd04d089c088f6a3c524f3f Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 22:25:02 +0800 Subject: [PATCH 55/78] user password support salt --- .../com/monkeyk/os/domain/users/Users.java | 23 +++++++++++++ .../os/infrastructure/shiro/MkkJdbcRealm.java | 6 ++-- .../monkeyk/os/domain/users/UsersTest.java | 33 +++++++++++++++++++ others/database/initial-db.ddl | 6 ++-- others/database/oauth2-shiro.ddl | 1 + 5 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java diff --git a/core/src/main/java/com/monkeyk/os/domain/users/Users.java b/core/src/main/java/com/monkeyk/os/domain/users/Users.java index 979197e..199d24e 100644 --- a/core/src/main/java/com/monkeyk/os/domain/users/Users.java +++ b/core/src/main/java/com/monkeyk/os/domain/users/Users.java @@ -23,6 +23,12 @@ public class Users extends AbstractDomain { private String username; private String password; + /** + * 密码 salt, 盐值 + * + * @since 2.0.0 + */ + private String passwordSalt; private boolean defaultUser; private Date lastLoginTime; @@ -30,6 +36,23 @@ public class Users extends AbstractDomain { public Users() { } + /** + * @return salt or null + * @since 2.0.0 + */ + public String passwordSalt() { + return passwordSalt; + } + + /** + * @param passwordSalt salt + * @return this + * @since 2.0.0 + */ + public Users passwordSalt(String passwordSalt) { + this.passwordSalt = passwordSalt; + return this; + } public List rolesList() { return usersRepository.findUsersRolesList(this.guid); diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java b/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java index 3b94a02..724e936 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/shiro/MkkJdbcRealm.java @@ -14,7 +14,7 @@ import org.springframework.beans.factory.InitializingBean; */ public class MkkJdbcRealm extends JdbcRealm implements InitializingBean { - public static final String AUTHENTICATION_QUERY = "select password from users where archived = 0 and username = ?"; + public static final String AUTHENTICATION_QUERY = "select password, password_salt from users where archived = 0 and username = ?"; public static final String USER_ROLES_QUERY = "select r.role_name from user_roles ur,users u,roles r where ur.users_id = u.id and ur.roles_id = r.id and u.username = ?"; @@ -23,13 +23,15 @@ public class MkkJdbcRealm extends JdbcRealm implements InitializingBean { public MkkJdbcRealm() { super(); + //support salt since 2.0.0 + setSaltStyle(SaltStyle.COLUMN); } /** * 根据实现的需要, 可以修改具体使用时的查询语句 * - * @throws Exception + * @throws Exception e */ @Override public void afterPropertiesSet() throws Exception { diff --git a/core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java b/core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java new file mode 100644 index 0000000..ed4b8c4 --- /dev/null +++ b/core/src/test/java/com/monkeyk/os/domain/users/UsersTest.java @@ -0,0 +1,33 @@ +package com.monkeyk.os.domain.users; + +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.util.ByteSource; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * 2023/9/25 22:18 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +class UsersTest { + + + @Test + void passwordSalt() { + + SecureRandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator(); + ByteSource byteSource = randomNumberGenerator.nextBytes(); + assertNotNull(byteSource); + assertNotNull(byteSource.toHex()); +// System.out.println(byteSource.toHex()); + + + ByteSource bytes = ByteSource.Util.bytes(byteSource.toHex()); + assertNotNull(bytes); + + } + +} \ No newline at end of file diff --git a/others/database/initial-db.ddl b/others/database/initial-db.ddl index 2a69332..60836ee 100644 --- a/others/database/initial-db.ddl +++ b/others/database/initial-db.ddl @@ -6,10 +6,10 @@ set names utf8; -- Two users: admin/Admin@2015#; test/Test@2015# truncate users; -insert into users(id,guid,create_time,username,password,default_user) +insert into users(id,guid,create_time,username,password,password_salt,default_user) values -(21,uuid(),now(),'admin','9e44eb14dc2f051343b0dc92d935ff4ae49aa626a8fc08964172c5d18f3fc3a9',1), -(22,uuid(),now(),'test','88d67a920783fd411b30943c3310a25f605e40406d24cf6f17bc6b20a379ecd6',0); +(21,uuid(),now(),'admin','9e44eb14dc2f051343b0dc92d935ff4ae49aa626a8fc08964172c5d18f3fc3a9','75fbe11d6f70e77b256121d7c3d5c412',1), +(22,uuid(),now(),'test','88d67a920783fd411b30943c3310a25f605e40406d24cf6f17bc6b20a379ecd6','5602aa9866ca612e66dbb7f7c9a1d3b7',0); -- Two roles: User,Admin diff --git a/others/database/oauth2-shiro.ddl b/others/database/oauth2-shiro.ddl index 9ac9958..ee6668b 100644 --- a/others/database/oauth2-shiro.ddl +++ b/others/database/oauth2-shiro.ddl @@ -23,6 +23,7 @@ CREATE TABLE users ( archived tinyint(1) default '0', version int(11) DEFAULT 0, password varchar(255) not null, + password_salt varchar(255), username varchar(255) not null unique, default_user tinyint(1) default '0', last_login_time datetime , -- Gitee From 12f9d5e7de8ab0543b499d7e92f97af6abb7a2c8 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 23:01:47 +0800 Subject: [PATCH 56/78] user password support salt --- .../os/domain/users/AuthzPasswordEncoder.java | 10 ++++--- .../users/password/MD5PasswordEncoder.java | 8 ++--- .../users/password/PasswordEncoder.java | 6 ++-- .../users/password/ShaPasswordEncoder.java | 8 ++--- .../os/service/business/UsersFormSaver.java | 2 +- .../monkeyk/os/service/dto/UsersFormDto.java | 2 ++ .../users/AuthzPasswordEncoderTest.java | 10 ++++--- .../password/MD5PasswordEncoderTest.java | 7 +++-- .../password/ShaPasswordEncoderTest.java | 15 +++++----- .../os/domain/shared/GuidGenerator.java | 30 +++++++++++++++++++ .../os/domain/shared/GuidGeneratorTest.java | 13 ++++++-- 11 files changed, 80 insertions(+), 31 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java index 3426c9b..d8f21ee 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java @@ -50,10 +50,11 @@ public class AuthzPasswordEncoder implements InitializingBean { * 加密 * * @param rasPassword 原始密码 + * @param salt 盐 or null * @return 加密后的 */ - public String encode(String rasPassword) { - return this.passwordEncoder.encode(rasPassword); + public String encode(String rasPassword, Object salt) { + return this.passwordEncoder.encode(rasPassword, salt); } /** @@ -61,10 +62,11 @@ public class AuthzPasswordEncoder implements InitializingBean { * * @param rawPassword 原始密码 * @param encodedPassword 加密的密码 + * @param salt 盐 or null * @return true 校验正确,其他 false */ - public boolean matches(CharSequence rawPassword, String encodedPassword) { - return this.passwordEncoder.matches(rawPassword, encodedPassword); + public boolean matches(CharSequence rawPassword, String encodedPassword, Object salt) { + return this.passwordEncoder.matches(rawPassword, encodedPassword, salt); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java index 866956d..c6eda81 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java @@ -14,14 +14,14 @@ import java.security.MessageDigest; public class MD5PasswordEncoder implements PasswordEncoder { @Override - public String encode(CharSequence rawPassword) { - Md5Hash md5Hash = new Md5Hash(rawPassword); + public String encode(CharSequence rawPassword, Object salt) { + Md5Hash md5Hash = new Md5Hash(rawPassword, salt); return md5Hash.toHex(); } @Override - public boolean matches(CharSequence rawPassword, String encodedPassword) { - String encode = this.encode(rawPassword); + public boolean matches(CharSequence rawPassword, String encodedPassword, Object salt) { + String encode = this.encode(rawPassword, salt); return MessageDigest.isEqual(encode.getBytes(StandardCharsets.UTF_8), encodedPassword.getBytes(StandardCharsets.UTF_8)); } } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java index 402f43a..cde974a 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java @@ -15,9 +15,10 @@ public interface PasswordEncoder { * 加密密码 * * @param rawPassword 原始密码 + * @param salt 盐 or null * @return 加密后的密码 */ - String encode(CharSequence rawPassword); + String encode(CharSequence rawPassword, Object salt); /** @@ -25,8 +26,9 @@ public interface PasswordEncoder { * * @param rawPassword 原始密码 * @param encodedPassword 加密的密码 + * @param salt 盐 or null * @return true 校验正确,其他 false */ - boolean matches(CharSequence rawPassword, String encodedPassword); + boolean matches(CharSequence rawPassword, String encodedPassword, Object salt); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java index 7466ab8..14464fc 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java @@ -49,14 +49,14 @@ public class ShaPasswordEncoder implements PasswordEncoder { } @Override - public String encode(CharSequence rawPassword) { - SimpleHash simpleHash = new SimpleHash(this.alg, rawPassword); + public String encode(CharSequence rawPassword, Object salt) { + SimpleHash simpleHash = new SimpleHash(this.alg, rawPassword, salt); return simpleHash.toHex(); } @Override - public boolean matches(CharSequence rawPassword, String encodedPassword) { - String encode = this.encode(rawPassword); + public boolean matches(CharSequence rawPassword, String encodedPassword, Object salt) { + String encode = this.encode(rawPassword, salt); return MessageDigest.isEqual( encode.getBytes(StandardCharsets.UTF_8), encodedPassword.getBytes(StandardCharsets.UTF_8)); diff --git a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java index a226fe0..2a82914 100644 --- a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java +++ b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java @@ -33,7 +33,7 @@ public class UsersFormSaver { Users users = formDto.newUsers(); //用指定的加密方式 - users.password(this.passwordEncoder.encode(formDto.getPassword())); + users.password(this.passwordEncoder.encode(formDto.getPassword(), users.passwordSalt())); final int id = usersRepository.saveUsers(users); diff --git a/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java b/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java index c77d58d..93ceb87 100644 --- a/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java +++ b/authz/src/main/java/com/monkeyk/os/service/dto/UsersFormDto.java @@ -39,6 +39,8 @@ public class UsersFormDto extends UsersDto { return new Users() .username(getUsername()) // .password(PasswordHandler.md5(getPassword())) + // salt, since 2.0.0 + .passwordSalt(GuidGenerator.nextSaltHex()) .guid(GuidGenerator.generate()); } } diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java index baf0e7f..4972985 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java @@ -21,15 +21,17 @@ class AuthzPasswordEncoderTest { ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); String adminPwd = "Admin@2015#"; - String encode = passwordEncoder.encode(adminPwd); + String salt = "75fbe11d6f70e77b256121d7c3d5c412"; + String encode = passwordEncoder.encode(adminPwd, salt); assertNotNull(encode); - assertEquals("9e44eb14dc2f051343b0dc92d935ff4ae49aa626a8fc08964172c5d18f3fc3a9", encode); + assertEquals("8d89f774ffd45ea7637ece040c58dd8bef4dc5a2887cc3cbd9a9a120acc8a476", encode); // System.out.println(encode); String testPwd = "Test@2015#"; - String encode2 = passwordEncoder.encode(testPwd); + String testSalt = "5602aa9866ca612e66dbb7f7c9a1d3b7"; + String encode2 = passwordEncoder.encode(testPwd, testSalt); assertNotNull(encode2); - assertEquals("88d67a920783fd411b30943c3310a25f605e40406d24cf6f17bc6b20a379ecd6", encode2); + assertEquals("77057f2599ccb8f22d066f5383cbca9fd80ebf4bd69f0f3cebeafb0066687bbe", encode2); // System.out.println(encode2); diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java index f5b5fec..b013119 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java @@ -17,13 +17,14 @@ class MD5PasswordEncoderTest { MD5PasswordEncoder passwordEncoder = new MD5PasswordEncoder(); String rawPwd = "Admin@2023&"; - String encode = passwordEncoder.encode(rawPwd); + String salt = "75fbe11d6f70e77b256121d7c3d5c412"; + String encode = passwordEncoder.encode(rawPwd, salt); assertNotNull(encode); // System.out.println(encode); - assertEquals("eb733df7498c0e973c5c7bdfecd1016c", encode); + assertEquals("28f3b1d853dbb4cf4642f88482c61796", encode); - boolean matches = passwordEncoder.matches(rawPwd, encode); + boolean matches = passwordEncoder.matches(rawPwd, encode, salt); assertTrue(matches); } diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java index c6dc965..17ac89a 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java @@ -17,14 +17,15 @@ class ShaPasswordEncoderTest { void encode256() { String rawPwd = "Admin@2023&"; + String salt = "5602aa9866ca612e66dbb7f7c9a1d3b7"; ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); - String encode = passwordEncoder.encode(rawPwd); + String encode = passwordEncoder.encode(rawPwd, salt); assertNotNull(encode); - assertEquals("cf41c9f84f0b08aa184f9031e402602a69474ca6d6e50964c58250f1938eb8f3", encode); + assertEquals("cffa9de2fa61f8696af0f9fe3999dadf43f71cbe990e46726c5ee67e7932e593", encode); // System.out.println(encode); - boolean matches = passwordEncoder.matches(rawPwd, encode); + boolean matches = passwordEncoder.matches(rawPwd, encode, salt); assertTrue(matches); } @@ -34,13 +35,13 @@ class ShaPasswordEncoderTest { String rawPwd = "Admin@2023&"; ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(384); - String encode = passwordEncoder.encode(rawPwd); + String encode = passwordEncoder.encode(rawPwd, null); assertNotNull(encode); assertEquals("0a32b1b88f37ad21ac65aae125cec7814e5535d1077a4a7e6ccbf48a16800603e04c6909d9a19c68080a248b7ca11034", encode); // System.out.println(encode); - boolean matches = passwordEncoder.matches(rawPwd, encode); + boolean matches = passwordEncoder.matches(rawPwd, encode, null); assertTrue(matches); } @@ -50,7 +51,7 @@ class ShaPasswordEncoderTest { String rawPwd = "Admin@2023&"; ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(512); - String encode = passwordEncoder.encode(rawPwd); + String encode = passwordEncoder.encode(rawPwd, null); assertNotNull(encode); assertEquals( @@ -58,7 +59,7 @@ class ShaPasswordEncoderTest { encode); // System.out.println(encode); - boolean matches = passwordEncoder.matches(rawPwd, encode); + boolean matches = passwordEncoder.matches(rawPwd, encode, null); assertTrue(matches); } diff --git a/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java b/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java index be5a69f..935fe10 100644 --- a/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java +++ b/core/src/main/java/com/monkeyk/os/domain/shared/GuidGenerator.java @@ -1,6 +1,8 @@ package com.monkeyk.os.domain.shared; import org.apache.commons.lang.RandomStringUtils; +import org.apache.shiro.crypto.SecureRandomNumberGenerator; +import org.apache.shiro.util.ByteSource; /** * @author Shengzhao Li @@ -8,9 +10,37 @@ import org.apache.commons.lang.RandomStringUtils; public abstract class GuidGenerator { + /** + * salt generator + * + * @since 2.0.0 + */ + private static final SecureRandomNumberGenerator SALT_NUMBER_GENERATOR = new SecureRandomNumberGenerator(); + private GuidGenerator() { } + + /** + * 生成一个随机的 salt + * + * @return ByteSource + * @since 2.0.0 + */ + public static ByteSource nextSalt() { + return SALT_NUMBER_GENERATOR.nextBytes(); + } + + /** + * 生成一个随机的 salt, hex + * + * @return hex string + * @since 2.0.0 + */ + public static String nextSaltHex() { + return nextSalt().toHex(); + } + public static String generate() { return RandomStringUtils.random(32, true, true); } diff --git a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java index ed94697..15d61c7 100644 --- a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java +++ b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java @@ -6,10 +6,19 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; /* - * @author Shengzhao Li - */ + * @author Shengzhao Li + */ public class GuidGeneratorTest { + + @Test + void nextSaltHex() { + + String salt = GuidGenerator.nextSaltHex(); + assertNotNull(salt); + assertTrue(salt.length() > 8); + } + @Test public void testGenerateClientId() throws Exception { final String clientId = GuidGenerator.generateClientId(); -- Gitee From 2990124d04dca2756ed4e301db241513c5e84b86 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Mon, 25 Sep 2023 23:02:21 +0800 Subject: [PATCH 57/78] user password support salt --- others/database/initial-db.ddl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/others/database/initial-db.ddl b/others/database/initial-db.ddl index 60836ee..b171a74 100644 --- a/others/database/initial-db.ddl +++ b/others/database/initial-db.ddl @@ -8,8 +8,8 @@ set names utf8; truncate users; insert into users(id,guid,create_time,username,password,password_salt,default_user) values -(21,uuid(),now(),'admin','9e44eb14dc2f051343b0dc92d935ff4ae49aa626a8fc08964172c5d18f3fc3a9','75fbe11d6f70e77b256121d7c3d5c412',1), -(22,uuid(),now(),'test','88d67a920783fd411b30943c3310a25f605e40406d24cf6f17bc6b20a379ecd6','5602aa9866ca612e66dbb7f7c9a1d3b7',0); +(21,uuid(),now(),'admin','8d89f774ffd45ea7637ece040c58dd8bef4dc5a2887cc3cbd9a9a120acc8a476','75fbe11d6f70e77b256121d7c3d5c412',1), +(22,uuid(),now(),'test','77057f2599ccb8f22d066f5383cbca9fd80ebf4bd69f0f3cebeafb0066687bbe','5602aa9866ca612e66dbb7f7c9a1d3b7',0); -- Two roles: User,Admin -- Gitee From 8bb4fa45794a427e232aa5026851d5cedee456e2 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 26 Sep 2023 14:25:14 +0800 Subject: [PATCH 58/78] user password support salt, testing --- .../os/domain/users/AuthzPasswordEncoder.java | 2 +- .../users/password/MD5PasswordEncoder.java | 4 ++-- .../domain/users/password/PasswordEncoder.java | 4 ++-- .../users/password/ShaPasswordEncoder.java | 7 ++++--- .../domain/users/AuthzPasswordEncoderTest.java | 17 ++++++++++++----- others/database/initial-db.ddl | 6 +++--- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java index d8f21ee..86bc25e 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java @@ -65,7 +65,7 @@ public class AuthzPasswordEncoder implements InitializingBean { * @param salt 盐 or null * @return true 校验正确,其他 false */ - public boolean matches(CharSequence rawPassword, String encodedPassword, Object salt) { + public boolean matches(String rawPassword, String encodedPassword, Object salt) { return this.passwordEncoder.matches(rawPassword, encodedPassword, salt); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java index c6eda81..8eae5b1 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java @@ -14,13 +14,13 @@ import java.security.MessageDigest; public class MD5PasswordEncoder implements PasswordEncoder { @Override - public String encode(CharSequence rawPassword, Object salt) { + public String encode(String rawPassword, Object salt) { Md5Hash md5Hash = new Md5Hash(rawPassword, salt); return md5Hash.toHex(); } @Override - public boolean matches(CharSequence rawPassword, String encodedPassword, Object salt) { + public boolean matches(String rawPassword, String encodedPassword, Object salt) { String encode = this.encode(rawPassword, salt); return MessageDigest.isEqual(encode.getBytes(StandardCharsets.UTF_8), encodedPassword.getBytes(StandardCharsets.UTF_8)); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java index cde974a..df28a99 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java @@ -18,7 +18,7 @@ public interface PasswordEncoder { * @param salt 盐 or null * @return 加密后的密码 */ - String encode(CharSequence rawPassword, Object salt); + String encode(String rawPassword, Object salt); /** @@ -29,6 +29,6 @@ public interface PasswordEncoder { * @param salt 盐 or null * @return true 校验正确,其他 false */ - boolean matches(CharSequence rawPassword, String encodedPassword, Object salt); + boolean matches(String rawPassword, String encodedPassword, Object salt); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java index 14464fc..b0daa57 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java @@ -49,13 +49,14 @@ public class ShaPasswordEncoder implements PasswordEncoder { } @Override - public String encode(CharSequence rawPassword, Object salt) { - SimpleHash simpleHash = new SimpleHash(this.alg, rawPassword, salt); + public String encode(String rawPassword, Object salt) { + char[] pwdChar = rawPassword.toCharArray(); + SimpleHash simpleHash = new SimpleHash(this.alg, pwdChar, salt); return simpleHash.toHex(); } @Override - public boolean matches(CharSequence rawPassword, String encodedPassword, Object salt) { + public boolean matches(String rawPassword, String encodedPassword, Object salt) { String encode = this.encode(rawPassword, salt); return MessageDigest.isEqual( encode.getBytes(StandardCharsets.UTF_8), diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java index 4972985..e637ef5 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/AuthzPasswordEncoderTest.java @@ -1,8 +1,12 @@ package com.monkeyk.os.domain.users; import com.monkeyk.os.domain.users.password.ShaPasswordEncoder; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.util.ByteSource; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; + import static org.junit.jupiter.api.Assertions.*; /** @@ -22,18 +26,21 @@ class AuthzPasswordEncoderTest { String adminPwd = "Admin@2015#"; String salt = "75fbe11d6f70e77b256121d7c3d5c412"; - String encode = passwordEncoder.encode(adminPwd, salt); + ByteSource bytesx = ByteSource.Util.bytes(Base64.decode(salt)); + String encode = passwordEncoder.encode(adminPwd, bytesx); assertNotNull(encode); - assertEquals("8d89f774ffd45ea7637ece040c58dd8bef4dc5a2887cc3cbd9a9a120acc8a476", encode); + assertEquals("7242fb20c63882a6664742e1f9e1ed77e13b74d601cbe5fb11430d24768e808b", encode); // System.out.println(encode); + assertTrue(passwordEncoder.matches(adminPwd, encode, bytesx)); String testPwd = "Test@2015#"; String testSalt = "5602aa9866ca612e66dbb7f7c9a1d3b7"; - String encode2 = passwordEncoder.encode(testPwd, testSalt); + ByteSource bytes = ByteSource.Util.bytes(Base64.decode(testSalt)); + String encode2 = passwordEncoder.encode(testPwd, bytes); assertNotNull(encode2); - assertEquals("77057f2599ccb8f22d066f5383cbca9fd80ebf4bd69f0f3cebeafb0066687bbe", encode2); + assertEquals("beef64b3218e0c93051119bde87782ed7b932169228c091464055369336f5044", encode2); // System.out.println(encode2); - + assertTrue(passwordEncoder.matches(testPwd, encode2, bytes)); } diff --git a/others/database/initial-db.ddl b/others/database/initial-db.ddl index b171a74..7587c18 100644 --- a/others/database/initial-db.ddl +++ b/others/database/initial-db.ddl @@ -4,12 +4,12 @@ set names utf8; --- Two users: admin/Admin@2015#; test/Test@2015# +-- Two users: admin/Admin@2015# test/Test@2015# truncate users; insert into users(id,guid,create_time,username,password,password_salt,default_user) values -(21,uuid(),now(),'admin','8d89f774ffd45ea7637ece040c58dd8bef4dc5a2887cc3cbd9a9a120acc8a476','75fbe11d6f70e77b256121d7c3d5c412',1), -(22,uuid(),now(),'test','77057f2599ccb8f22d066f5383cbca9fd80ebf4bd69f0f3cebeafb0066687bbe','5602aa9866ca612e66dbb7f7c9a1d3b7',0); +(21,uuid(),now(),'admin','7242fb20c63882a6664742e1f9e1ed77e13b74d601cbe5fb11430d24768e808b','75fbe11d6f70e77b256121d7c3d5c412',1), +(22,uuid(),now(),'test','beef64b3218e0c93051119bde87782ed7b932169228c091464055369336f5044','5602aa9866ca612e66dbb7f7c9a1d3b7',0); -- Two roles: User,Admin -- Gitee From af92e7776bee0ae5443e11cdc768ef9f81b0c1e7 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 26 Sep 2023 14:31:50 +0800 Subject: [PATCH 59/78] user password support salt, testing --- .../monkeyk/os/domain/users/AuthzPasswordEncoder.java | 5 +++-- .../os/domain/users/password/MD5PasswordEncoder.java | 5 +++-- .../os/domain/users/password/PasswordEncoder.java | 6 ++++-- .../os/domain/users/password/ShaPasswordEncoder.java | 5 +++-- .../com/monkeyk/os/service/business/UsersFormSaver.java | 5 ++++- .../os/domain/users/password/MD5PasswordEncoderTest.java | 9 ++++++--- .../os/domain/users/password/ShaPasswordEncoderTest.java | 9 ++++++--- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java index 86bc25e..706e27e 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/AuthzPasswordEncoder.java @@ -5,6 +5,7 @@ import com.monkeyk.os.domain.users.password.PasswordEncoder; import com.monkeyk.os.domain.users.password.ShaPasswordEncoder; import org.apache.shiro.crypto.hash.*; import org.apache.shiro.util.Assert; +import org.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; @@ -53,7 +54,7 @@ public class AuthzPasswordEncoder implements InitializingBean { * @param salt 盐 or null * @return 加密后的 */ - public String encode(String rasPassword, Object salt) { + public String encode(String rasPassword, ByteSource salt) { return this.passwordEncoder.encode(rasPassword, salt); } @@ -65,7 +66,7 @@ public class AuthzPasswordEncoder implements InitializingBean { * @param salt 盐 or null * @return true 校验正确,其他 false */ - public boolean matches(String rawPassword, String encodedPassword, Object salt) { + public boolean matches(String rawPassword, String encodedPassword, ByteSource salt) { return this.passwordEncoder.matches(rawPassword, encodedPassword, salt); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java index 8eae5b1..b8ef477 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoder.java @@ -1,6 +1,7 @@ package com.monkeyk.os.domain.users.password; import org.apache.shiro.crypto.hash.Md5Hash; +import org.apache.shiro.util.ByteSource; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -14,13 +15,13 @@ import java.security.MessageDigest; public class MD5PasswordEncoder implements PasswordEncoder { @Override - public String encode(String rawPassword, Object salt) { + public String encode(String rawPassword, ByteSource salt) { Md5Hash md5Hash = new Md5Hash(rawPassword, salt); return md5Hash.toHex(); } @Override - public boolean matches(String rawPassword, String encodedPassword, Object salt) { + public boolean matches(String rawPassword, String encodedPassword, ByteSource salt) { String encode = this.encode(rawPassword, salt); return MessageDigest.isEqual(encode.getBytes(StandardCharsets.UTF_8), encodedPassword.getBytes(StandardCharsets.UTF_8)); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java index df28a99..99aa064 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/PasswordEncoder.java @@ -1,5 +1,7 @@ package com.monkeyk.os.domain.users.password; +import org.apache.shiro.util.ByteSource; + /** * 2023/9/25 16:19 *

    @@ -18,7 +20,7 @@ public interface PasswordEncoder { * @param salt 盐 or null * @return 加密后的密码 */ - String encode(String rawPassword, Object salt); + String encode(String rawPassword, ByteSource salt); /** @@ -29,6 +31,6 @@ public interface PasswordEncoder { * @param salt 盐 or null * @return true 校验正确,其他 false */ - boolean matches(String rawPassword, String encodedPassword, Object salt); + boolean matches(String rawPassword, String encodedPassword, ByteSource salt); } diff --git a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java index b0daa57..3c8c84c 100644 --- a/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java +++ b/authz/src/main/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoder.java @@ -1,6 +1,7 @@ package com.monkeyk.os.domain.users.password; import org.apache.shiro.crypto.hash.SimpleHash; +import org.apache.shiro.util.ByteSource; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -49,14 +50,14 @@ public class ShaPasswordEncoder implements PasswordEncoder { } @Override - public String encode(String rawPassword, Object salt) { + public String encode(String rawPassword, ByteSource salt) { char[] pwdChar = rawPassword.toCharArray(); SimpleHash simpleHash = new SimpleHash(this.alg, pwdChar, salt); return simpleHash.toHex(); } @Override - public boolean matches(String rawPassword, String encodedPassword, Object salt) { + public boolean matches(String rawPassword, String encodedPassword, ByteSource salt) { String encode = this.encode(rawPassword, salt); return MessageDigest.isEqual( encode.getBytes(StandardCharsets.UTF_8), diff --git a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java index 2a82914..12941f4 100644 --- a/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java +++ b/authz/src/main/java/com/monkeyk/os/service/business/UsersFormSaver.java @@ -5,6 +5,8 @@ import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; import com.monkeyk.os.domain.users.UsersAuthzRepository; import com.monkeyk.os.service.dto.UsersFormDto; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -33,7 +35,8 @@ public class UsersFormSaver { Users users = formDto.newUsers(); //用指定的加密方式 - users.password(this.passwordEncoder.encode(formDto.getPassword(), users.passwordSalt())); + ByteSource salt = ByteSource.Util.bytes(Base64.decode(users.passwordSalt())); + users.password(this.passwordEncoder.encode(formDto.getPassword(), salt)); final int id = usersRepository.saveUsers(users); diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java index b013119..709259f 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/MD5PasswordEncoderTest.java @@ -1,6 +1,8 @@ package com.monkeyk.os.domain.users.password; +import org.apache.shiro.codec.Base64; import org.apache.shiro.crypto.hash.Md5Hash; +import org.apache.shiro.util.ByteSource; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -18,13 +20,14 @@ class MD5PasswordEncoderTest { MD5PasswordEncoder passwordEncoder = new MD5PasswordEncoder(); String rawPwd = "Admin@2023&"; String salt = "75fbe11d6f70e77b256121d7c3d5c412"; - String encode = passwordEncoder.encode(rawPwd, salt); + ByteSource saltBytes = ByteSource.Util.bytes(Base64.decode(salt)); + String encode = passwordEncoder.encode(rawPwd, saltBytes); assertNotNull(encode); // System.out.println(encode); - assertEquals("28f3b1d853dbb4cf4642f88482c61796", encode); + assertEquals("fa1250c1ddefb1645a5507e8a7d76c99", encode); - boolean matches = passwordEncoder.matches(rawPwd, encode, salt); + boolean matches = passwordEncoder.matches(rawPwd, encode, saltBytes); assertTrue(matches); } diff --git a/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java index 17ac89a..739b3d0 100644 --- a/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java +++ b/authz/src/test/java/com/monkeyk/os/domain/users/password/ShaPasswordEncoderTest.java @@ -1,5 +1,7 @@ package com.monkeyk.os.domain.users.password; +import org.apache.shiro.codec.Base64; +import org.apache.shiro.util.ByteSource; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -19,13 +21,14 @@ class ShaPasswordEncoderTest { String rawPwd = "Admin@2023&"; String salt = "5602aa9866ca612e66dbb7f7c9a1d3b7"; ShaPasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); - String encode = passwordEncoder.encode(rawPwd, salt); + ByteSource bytes = ByteSource.Util.bytes(Base64.decode(salt)); + String encode = passwordEncoder.encode(rawPwd, bytes); assertNotNull(encode); - assertEquals("cffa9de2fa61f8696af0f9fe3999dadf43f71cbe990e46726c5ee67e7932e593", encode); + assertEquals("34f37824cf8008f47d777ce9954eff34f2de984f2dc6019952b7bc8d72794ddf", encode); // System.out.println(encode); - boolean matches = passwordEncoder.matches(rawPwd, encode, salt); + boolean matches = passwordEncoder.matches(rawPwd, encode, bytes); assertTrue(matches); } -- Gitee From dd0c2a8a69920d79f4d322eff507b7f8c608fa4d Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 26 Sep 2023 14:48:46 +0800 Subject: [PATCH 60/78] user password support salt, testing --- .../jdbc/UsersJdbcAuthzRepository.java | 44 ++++++++++--------- authz/src/main/resources/templates/login.html | 2 +- .../templates/users/users_overview.html | 3 ++ .../infrastructure/jdbc/UsersRowMapper.java | 2 + 4 files changed, 30 insertions(+), 21 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java index f9ebb05..f1f1dbe 100644 --- a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java +++ b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersJdbcAuthzRepository.java @@ -4,6 +4,8 @@ import com.monkeyk.os.domain.users.Roles; import com.monkeyk.os.domain.users.Users; import com.monkeyk.os.domain.users.UsersAuthzRepository; import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.jdbc.core.PreparedStatementSetter; import org.springframework.stereotype.Repository; @@ -22,9 +24,10 @@ import java.util.List; @Repository("usersJdbcAuthzRepository") public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements UsersAuthzRepository { + private static final Logger LOG = LoggerFactory.getLogger(UsersJdbcAuthzRepository.class); - private static UsersRowMapper usersRowMapper = new UsersRowMapper(); - private static RolesRowMapper rolesRowMapper = new RolesRowMapper(); + private final UsersRowMapper usersRowMapper = new UsersRowMapper(); + private final RolesRowMapper rolesRowMapper = new RolesRowMapper(); @Override @@ -54,19 +57,20 @@ public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements @Override public int saveUsers(final Users users) { - String sql = " insert into users(guid,create_time, username,password) values (?,?,?,?) "; - this.jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, users.guid()); - ps.setTimestamp(2, new Timestamp(users.createTime().getTime())); - ps.setString(3, users.username()); - - ps.setString(4, users.password()); - } + String sql = " insert into users(guid,create_time, username,password, password_salt) values (?,?,?,?,?) "; + int row = this.jdbcTemplate.update(sql, ps -> { + ps.setString(1, users.guid()); + ps.setTimestamp(2, new Timestamp(users.createTime().getTime())); + ps.setString(3, users.username()); + + ps.setString(4, users.password()); + ps.setString(5, users.passwordSalt()); }); + if (LOG.isDebugEnabled()) { + LOG.debug("Insert into users -> row: {}", row); + } - return this.jdbcTemplate.queryForObject("select id from users where guid = ?", new Object[]{users.guid()}, Integer.class); + return this.jdbcTemplate.queryForObject("select id from users where guid = ?", Integer.class, new Object[]{users.guid()}); } @Override @@ -78,13 +82,13 @@ public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements @Override public void insertUserRoles(final int userId, final int rolesId) { String sql = "insert into user_roles(users_id,roles_id) values (?,?) "; - this.jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setInt(1, userId); - ps.setInt(2, rolesId); - } + int row = this.jdbcTemplate.update(sql, ps -> { + ps.setInt(1, userId); + ps.setInt(2, rolesId); }); + if (LOG.isDebugEnabled()) { + LOG.debug("Insert into user_roles -> row: {}", row); + } } @Override @@ -99,6 +103,6 @@ public class UsersJdbcAuthzRepository extends AbstractJdbcRepository implements public List findPermissionsByRoles(String rolesGuid) { String sql = " select p.permission from roles_permissions p where p.roles_id = (" + " select id from roles where guid = ? and archived = 0 )"; - return this.jdbcTemplate.queryForList(sql, new Object[]{rolesGuid}, String.class); + return this.jdbcTemplate.queryForList(sql, String.class, new Object[]{rolesGuid}); } } diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html index b7200a6..e7ba867 100644 --- a/authz/src/main/resources/templates/login.html +++ b/authz/src/main/resources/templates/login.html @@ -22,7 +22,7 @@ 操作说明

    1. -

      登录系统,使用初始的账号 test/test 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

      +

      登录系统,使用初始的账号 test/Test@2015# 或去 Users 先创建用户, 这用于测试Shiro安全是否工作

      diff --git a/authz/src/main/resources/templates/users/users_overview.html b/authz/src/main/resources/templates/users/users_overview.html index e834de9..daaa622 100644 --- a/authz/src/main/resources/templates/users/users_overview.html +++ b/authz/src/main/resources/templates/users/users_overview.html @@ -16,6 +16,9 @@

      Users

      +
      + 说明 Users管理正常需要有管理权限(如Admin权限)的账号登录后才能操作,此处未登录就能查看与添加是方便演示功能。 +
      diff --git a/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java b/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java index 212ebd9..e5ff62c 100644 --- a/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java +++ b/core/src/main/java/com/monkeyk/os/infrastructure/jdbc/UsersRowMapper.java @@ -21,6 +21,8 @@ public class UsersRowMapper implements RowMapper { Users users = new Users() .username(rs.getString("username")) .password(rs.getString("password")) + // salt 不需要查询出来 +// .passwordSalt(rs.getString("password_salt")) .defaultUser(rs.getBoolean("default_user")) .lastLoginTime(rs.getTimestamp("last_login_time")); -- Gitee From 529f5ed6d80dc3a56d70f7340b749fd796ad3f9b Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 26 Sep 2023 14:49:42 +0800 Subject: [PATCH 61/78] user password support salt, testing --- authz/src/main/resources/templates/users/users_overview.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/authz/src/main/resources/templates/users/users_overview.html b/authz/src/main/resources/templates/users/users_overview.html index daaa622..f8f1659 100644 --- a/authz/src/main/resources/templates/users/users_overview.html +++ b/authz/src/main/resources/templates/users/users_overview.html @@ -17,7 +17,7 @@

      Users

      - 说明 Users管理正常需要有管理权限(如Admin权限)的账号登录后才能操作,此处未登录就能查看与添加是方便演示功能。 + 说明: Users管理正常需要有管理权限(如Admin权限)的账号登录后才能操作,此处未登录就能查看与添加是方便演示功能。
      Add User -- Gitee From 8f3ccf45a3ae081ffa7e5cbabc32da5869ea29dd Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Tue, 26 Sep 2023 15:19:53 +0800 Subject: [PATCH 62/78] token support jwt flow --- authz/pom.xml | 6 ---- .../monkeyk/os/config/AuthzContextConfig.java | 30 +++++++++++++++++-- .../os/web/context/BeanContextAware.java | 3 +- .../resources/static/html/oauth_test.html | 18 +++++------ .../templates/oauth/client_details_plus.html | 3 +- .../java/com/monkeyk/os/domain/Constants.java | 18 ----------- .../monkeyk/os/domain/oauth/Constants.java | 28 ++++++++++++----- .../os/web/context/BeanContextAware.java | 2 +- .../os/web/controller/WelcomeController.java | 2 +- 9 files changed, 61 insertions(+), 49 deletions(-) delete mode 100644 core/src/main/java/com/monkeyk/os/domain/Constants.java diff --git a/authz/pom.xml b/authz/pom.xml index 7befe15..ec4ade0 100644 --- a/authz/pom.xml +++ b/authz/pom.xml @@ -52,12 +52,6 @@ - - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.authzserver - ${oltu.version} - org.apache.oltu.oauth2 diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java index 9078716..67d09c3 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java @@ -1,10 +1,14 @@ package com.monkeyk.os.config; import com.monkeyk.os.domain.oauth.AuthenticationIdGenerator; +import com.monkeyk.os.domain.oauth.Constants; import com.monkeyk.os.domain.oauth.DefaultAuthenticationIdGenerator; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; + +import org.apache.oltu.oauth2.as.issuer.ValueGenerator; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; @@ -25,6 +29,16 @@ import javax.sql.DataSource; public class AuthzContextConfig { + /** + * 生成token的类型是 jwt 还是 md5 + * 默认 jwt + * + * @since 2.0.0 + */ + @Value("${authz.token.generator.type:jwt") + private String tokenGeneratorType; + + /** * 事务配置 */ @@ -53,13 +67,23 @@ public class AuthzContextConfig { } /** - * 使用MD5 OAuthIssuer, 生成随机值,如 access_token, refresh_token + * 默认使用MD5 OAuthIssuer, 生成随机值,如 access_token, refresh_token * 可根据需要扩展使用其他的实现 + *

      + * 根据配置参数authz.token.generator.type 来决定是用 jwt 还是 md5值 + * 可选值:md5 或 jwt (默认) */ @Bean public OAuthIssuer oAuthIssuer() { - MD5Generator md5Generator = new MD5Generator(); - return new OAuthIssuerImpl(md5Generator); + if (Constants.JWT.equalsIgnoreCase(this.tokenGeneratorType)) { + + + return new OAuthIssuerImpl(null); + } else { + //其他用md5, 兼容旧版本 + MD5Generator md5Generator = new MD5Generator(); + return new OAuthIssuerImpl(md5Generator); + } } diff --git a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java index e783b47..fc36dac 100644 --- a/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java +++ b/authz/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -8,7 +8,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; -import static com.monkeyk.os.domain.Constants.VERSION; +import static com.monkeyk.os.domain.oauth.Constants.VERSION; + /** * 2020/7/14 diff --git a/authz/src/main/resources/static/html/oauth_test.html b/authz/src/main/resources/static/html/oauth_test.html index f48483f..6b5773d 100644 --- a/authz/src/main/resources/static/html/oauth_test.html +++ b/authz/src/main/resources/static/html/oauth_test.html @@ -20,7 +20,7 @@

      -
      @@ -28,7 +28,7 @@
      -
      @@ -77,7 +77,7 @@
      -
      @@ -85,7 +85,7 @@
      -
    @@ -118,7 +118,7 @@
    + value="Test@2015#" required="true"/>
    @@ -143,7 +143,7 @@
    -
    @@ -151,7 +151,7 @@
    -
    @@ -190,7 +190,7 @@
    -
    @@ -198,7 +198,7 @@
    -
    diff --git a/authz/src/main/resources/templates/oauth/client_details_plus.html b/authz/src/main/resources/templates/oauth/client_details_plus.html index 7595c19..65edf29 100644 --- a/authz/src/main/resources/templates/oauth/client_details_plus.html +++ b/authz/src/main/resources/templates/oauth/client_details_plus.html @@ -133,7 +133,6 @@
    @@ -174,7 +173,7 @@ -

    给Client起一个名称

    +

    给Client起一个有意义的名称

    diff --git a/core/src/main/java/com/monkeyk/os/domain/Constants.java b/core/src/main/java/com/monkeyk/os/domain/Constants.java deleted file mode 100644 index e2f8b68..0000000 --- a/core/src/main/java/com/monkeyk/os/domain/Constants.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.monkeyk.os.domain; - -/** - * 2023/9/21 15:28 - * - * @author Shengzhao Li - * @since 2.0.0 - */ -public interface Constants { - - - /** - * Version - * 与pom.xml中一致 - */ - String VERSION = "2.0.0"; - -} diff --git a/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java b/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java index 72d65fa..8007667 100644 --- a/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java +++ b/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java @@ -7,19 +7,31 @@ package com.monkeyk.os.domain.oauth; * * @author Shengzhao Li */ -public abstract class Constants { +public interface Constants { - public static final String REQUEST_USERNAME = "username"; - public static final String REQUEST_PASSWORD = "password"; + String REQUEST_USERNAME = "username"; + String REQUEST_PASSWORD = "password"; - public static final String REQUEST_USER_OAUTH_APPROVAL = "user_oauth_approval"; + String REQUEST_USER_OAUTH_APPROVAL = "user_oauth_approval"; - public static final String OAUTH_LOGIN_VIEW = "oauth_login"; - public static final String OAUTH_APPROVAL_VIEW = "oauth_approval"; + String OAUTH_LOGIN_VIEW = "oauth_login"; + String OAUTH_APPROVAL_VIEW = "oauth_approval"; - private Constants() { - } + /** + * label: jwt + * + * @since 2.0.0 + */ + String JWT = "jwt"; + /** + * Version + * 与pom.xml中一致 + * + * @since 2.0.0 + */ + String VERSION = "2.0.0"; + } diff --git a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java index 8b02fa6..f3f3b23 100644 --- a/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java +++ b/resources/src/main/java/com/monkeyk/os/web/context/BeanContextAware.java @@ -8,7 +8,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; -import static com.monkeyk.os.domain.Constants.VERSION; +import static com.monkeyk.os.domain.oauth.Constants.VERSION; /** * 2020/7/14 diff --git a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java index 102da76..6b9d87f 100644 --- a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java +++ b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java @@ -3,7 +3,7 @@ package com.monkeyk.os.web.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import static com.monkeyk.os.domain.Constants.VERSION; +import static com.monkeyk.os.domain.oauth.Constants.VERSION; /** * 2023/9/21 12:22 -- Gitee From 50b8bd530fe4f7901bbb87cd2698faab3381f16e Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 27 Sep 2023 18:11:15 +0800 Subject: [PATCH 63/78] token support jwt flow --- .../monkeyk/os/config/AuthzContextConfig.java | 28 +---- .../jdbc/OauthJdbcRepository.java | 103 +++++++--------- .../token/AuthorizationCodeTokenHandler.java | 7 +- .../os/oauth/token/JwtTokenEnhancer.java | 115 ++++++++++++++++++ .../os/service/impl/OauthServiceImpl.java | 60 +++++++-- .../monkeyk/os/domain/oauth/Constants.java | 9 ++ .../os/domain/shared/GuidGeneratorTest.java | 10 ++ .../monkeyk/os/infrastructure/Jose4JTest.java | 46 ++++++- 8 files changed, 277 insertions(+), 101 deletions(-) create mode 100644 authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java diff --git a/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java index 67d09c3..e247bdf 100644 --- a/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java +++ b/authz/src/main/java/com/monkeyk/os/config/AuthzContextConfig.java @@ -1,14 +1,10 @@ package com.monkeyk.os.config; import com.monkeyk.os.domain.oauth.AuthenticationIdGenerator; -import com.monkeyk.os.domain.oauth.Constants; import com.monkeyk.os.domain.oauth.DefaultAuthenticationIdGenerator; import org.apache.oltu.oauth2.as.issuer.MD5Generator; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; - -import org.apache.oltu.oauth2.as.issuer.ValueGenerator; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.core.JdbcTemplate; @@ -29,16 +25,6 @@ import javax.sql.DataSource; public class AuthzContextConfig { - /** - * 生成token的类型是 jwt 还是 md5 - * 默认 jwt - * - * @since 2.0.0 - */ - @Value("${authz.token.generator.type:jwt") - private String tokenGeneratorType; - - /** * 事务配置 */ @@ -69,21 +55,11 @@ public class AuthzContextConfig { /** * 默认使用MD5 OAuthIssuer, 生成随机值,如 access_token, refresh_token * 可根据需要扩展使用其他的实现 - *

    - * 根据配置参数authz.token.generator.type 来决定是用 jwt 还是 md5值 - * 可选值:md5 或 jwt (默认) */ @Bean public OAuthIssuer oAuthIssuer() { - if (Constants.JWT.equalsIgnoreCase(this.tokenGeneratorType)) { - - - return new OAuthIssuerImpl(null); - } else { - //其他用md5, 兼容旧版本 - MD5Generator md5Generator = new MD5Generator(); - return new OAuthIssuerImpl(md5Generator); - } + MD5Generator md5Generator = new MD5Generator(); + return new OAuthIssuerImpl(md5Generator); } diff --git a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java index 72cb939..f4a1a07 100644 --- a/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java +++ b/authz/src/main/java/com/monkeyk/os/infrastructure/jdbc/OauthJdbcRepository.java @@ -23,10 +23,10 @@ import java.util.List; public class OauthJdbcRepository extends AbstractJdbcRepository implements OauthRepository { - private static ClientDetailsRowMapper clientDetailsRowMapper = new ClientDetailsRowMapper(); - private static OauthCodeRowMapper oauthCodeRowMapper = new OauthCodeRowMapper(); + private final ClientDetailsRowMapper clientDetailsRowMapper = new ClientDetailsRowMapper(); + private final OauthCodeRowMapper oauthCodeRowMapper = new OauthCodeRowMapper(); - private static AccessTokenRowMapper accessTokenRowMapper = new AccessTokenRowMapper(); + private final AccessTokenRowMapper accessTokenRowMapper = new AccessTokenRowMapper(); @Override @@ -41,42 +41,36 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth final String sql = " insert into oauth_client_details(client_id,client_secret,client_name, client_uri,client_icon_uri,resource_ids, scope,grant_types, " + "redirect_uri,roles,access_token_validity,refresh_token_validity,description,archived,trusted) values (?,?,?, ?,?,?,?,?, ?,?, ?,? ,?,?,?)"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, clientDetails.getClientId()); - ps.setString(2, clientDetails.getClientSecret()); - ps.setString(3, clientDetails.getName()); - - ps.setString(4, clientDetails.getClientUri()); - ps.setString(5, clientDetails.getIconUri()); - ps.setString(6, clientDetails.resourceIds()); - - ps.setString(7, clientDetails.scope()); - ps.setString(8, clientDetails.grantTypes()); - ps.setString(9, clientDetails.getRedirectUri()); - - ps.setString(10, clientDetails.roles()); - ps.setInt(11, clientDetails.accessTokenValidity() == null ? -1 : clientDetails.accessTokenValidity()); - ps.setInt(12, clientDetails.refreshTokenValidity() == null ? -1 : clientDetails.refreshTokenValidity()); - - ps.setString(13, clientDetails.getDescription()); - ps.setBoolean(14, clientDetails.archived()); - ps.setBoolean(15, clientDetails.trusted()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, clientDetails.getClientId()); + ps.setString(2, clientDetails.getClientSecret()); + ps.setString(3, clientDetails.getName()); + + ps.setString(4, clientDetails.getClientUri()); + ps.setString(5, clientDetails.getIconUri()); + ps.setString(6, clientDetails.resourceIds()); + + ps.setString(7, clientDetails.scope()); + ps.setString(8, clientDetails.grantTypes()); + ps.setString(9, clientDetails.getRedirectUri()); + + ps.setString(10, clientDetails.roles()); + ps.setInt(11, clientDetails.accessTokenValidity() == null ? -1 : clientDetails.accessTokenValidity()); + ps.setInt(12, clientDetails.refreshTokenValidity() == null ? -1 : clientDetails.refreshTokenValidity()); + + ps.setString(13, clientDetails.getDescription()); + ps.setBoolean(14, clientDetails.archived()); + ps.setBoolean(15, clientDetails.trusted()); }); } @Override public int saveOauthCode(final OauthCode oauthCode) { final String sql = " insert into oauth_code(code,username,client_id) values (?,?,?)"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, oauthCode.code()); - ps.setString(2, oauthCode.username()); - ps.setString(3, oauthCode.clientId()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, oauthCode.code()); + ps.setString(2, oauthCode.username()); + ps.setString(3, oauthCode.clientId()); }); } @@ -97,13 +91,10 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth @Override public int deleteOauthCode(final OauthCode oauthCode) { final String sql = " delete from oauth_code where code = ? and client_id = ? and username = ?"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, oauthCode.code()); - ps.setString(2, oauthCode.clientId()); - ps.setString(3, oauthCode.username()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, oauthCode.code()); + ps.setString(2, oauthCode.clientId()); + ps.setString(3, oauthCode.username()); }); } @@ -117,13 +108,10 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth @Override public int deleteAccessToken(final AccessToken accessToken) { final String sql = " delete from oauth_access_token where client_id = ? and username = ? and authentication_id = ?"; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, accessToken.clientId()); - ps.setString(2, accessToken.username()); - ps.setString(3, accessToken.authenticationId()); - } + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, accessToken.clientId()); + ps.setString(2, accessToken.username()); + ps.setString(3, accessToken.authenticationId()); }); } @@ -132,20 +120,17 @@ public class OauthJdbcRepository extends AbstractJdbcRepository implements Oauth final String sql = "insert into oauth_access_token(token_id,token_expired_seconds,authentication_id," + "username,client_id,token_type,refresh_token_expired_seconds,refresh_token) values (?,?,?,?,?,?,?,?) "; - return jdbcTemplate.update(sql, new PreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps) throws SQLException { - ps.setString(1, accessToken.tokenId()); - ps.setInt(2, accessToken.tokenExpiredSeconds()); - ps.setString(3, accessToken.authenticationId()); + return jdbcTemplate.update(sql, ps -> { + ps.setString(1, accessToken.tokenId()); + ps.setInt(2, accessToken.tokenExpiredSeconds()); + ps.setString(3, accessToken.authenticationId()); - ps.setString(4, accessToken.username()); - ps.setString(5, accessToken.clientId()); - ps.setString(6, accessToken.tokenType()); + ps.setString(4, accessToken.username()); + ps.setString(5, accessToken.clientId()); + ps.setString(6, accessToken.tokenType()); - ps.setInt(7, accessToken.refreshTokenExpiredSeconds()); - ps.setString(8, accessToken.refreshToken()); - } + ps.setInt(7, accessToken.refreshTokenExpiredSeconds()); + ps.setString(8, accessToken.refreshToken()); }); } diff --git a/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java b/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java index 7214f2d..1ff5bb3 100644 --- a/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java +++ b/authz/src/main/java/com/monkeyk/os/oauth/token/AuthorizationCodeTokenHandler.java @@ -40,10 +40,9 @@ public class AuthorizationCodeTokenHandler extends AbstractOAuthTokenHandler { return GrantType.AUTHORIZATION_CODE.toString().equalsIgnoreCase(grantType); } - /* - * - * /oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=zLl170&redirect_uri=redirect_uri - * */ + /** + * /oauth/token?client_id=unity-client&client_secret=unity&grant_type=authorization_code&code=zLl170&redirect_uri=redirect_uri + */ @Override public void handleAfterValidation() throws OAuthProblemException, OAuthSystemException { diff --git a/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java b/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java new file mode 100644 index 0000000..3b4bf7d --- /dev/null +++ b/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java @@ -0,0 +1,115 @@ +package com.monkeyk.os.oauth.token; + +import org.jose4j.jws.AlgorithmIdentifiers; +import org.jose4j.jws.JsonWebSignature; +import org.jose4j.jwt.JwtClaims; +import org.jose4j.jwt.NumericDate; +import org.jose4j.keys.HmacKey; +import org.jose4j.lang.JoseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; + +import static com.monkeyk.os.domain.oauth.Constants.DEFAULT_KEY_ID; + +/** + * 2023/9/26 15:22 + *

    + * JWT token 增强实现 + * + * @author Shengzhao Li + * @since 2.0.0 + */ +@Component +public class JwtTokenEnhancer implements InitializingBean { + + private static final Logger LOG = LoggerFactory.getLogger(JwtTokenEnhancer.class); + + + /** + * Jwt hamc key ; 长度至少32位 + * TODO: 不同的部署环境请使用不同的hmac key + * + * @since 2.0.0 + */ + @Value("${authz.token.jwt.hmac.key:Bl0depAUL2DRPZnR0DJThK9a9KSJF4Xr") + private String jwtHmacKey; + + + private HmacKey hmacKey; + + + public JwtTokenEnhancer() { + } + + + /** + * enhance jti + * + * @param jti token + * @param subject payload subject + * @param audience payload audience + * @param expiredSeconds token expired seconds + * @return jwt + */ + public String enhance(String jti, String subject, String audience, int expiredSeconds) { + return this.enhance(jti, subject, audience, expiredSeconds, Collections.emptyMap()); + } + + + /** + * enhance jti + * + * @param jti token + * @param subject payload subject + * @param audience payload audience + * @param expiredSeconds token expired seconds + * @param extPayloadMap ext payload map + * @return jwt + */ + public String enhance(String jti, String subject, String audience, int expiredSeconds, Map extPayloadMap) { + + JsonWebSignature jws = new JsonWebSignature(); + jws.setKeyIdHeaderValue(DEFAULT_KEY_ID); + jws.setKey(hmacKey); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); + + JwtClaims claims = new JwtClaims(); + claims.setSubject(subject); + claims.setJwtId(jti); +// claims.setIssuer("https://myoidc.com"); + claims.setIssuedAtToNow(); + + NumericDate now = NumericDate.now(); + now.addSeconds(expiredSeconds); + claims.setExpirationTime(now); + claims.setAudience(audience); + + for (String key : extPayloadMap.keySet()) { + claims.setStringClaim(key, extPayloadMap.get(key)); + } + + jws.setPayload(claims.toJson()); + + try { + return jws.getCompactSerialization(); + } catch (JoseException e) { + throw new IllegalStateException("Jwt enhance error", e); + } + } + + + @Override + public void afterPropertiesSet() throws Exception { + this.hmacKey = new HmacKey(jwtHmacKey.getBytes(StandardCharsets.UTF_8)); + if (LOG.isDebugEnabled()) { + LOG.debug("Initialized hmacKey: {}", this.hmacKey); + } + } +} diff --git a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java index 3b61bd2..382b2e5 100644 --- a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java +++ b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java @@ -1,6 +1,7 @@ package com.monkeyk.os.service.impl; import com.monkeyk.os.domain.oauth.*; +import com.monkeyk.os.oauth.token.JwtTokenEnhancer; import com.monkeyk.os.service.OauthService; import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; import org.apache.oltu.oauth2.common.exception.OAuthSystemException; @@ -9,6 +10,7 @@ import org.apache.shiro.SecurityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.util.Set; @@ -31,6 +33,29 @@ public class OauthServiceImpl implements OauthService { @Autowired private OAuthIssuer oAuthIssuer; + + /** + * 生成token的类型是 jwt 还是 md5 + * + *

    + * 根据配置参数authz.token.generator.type 来决定是用 jwt 还是 md5值 + * 可选值:md5 或 jwt (默认) + * + * @since 2.0.0 + */ + @Value("${authz.token.generator.type:jwt") + private String tokenGeneratorType; + + + /** + * 增加 token 为 jwt 格式 + * + * @since 2.0.0 + */ + @Autowired + private JwtTokenEnhancer jwtTokenEnhancer; + + @Override public ClientDetails loadClientDetails(String clientId) { LOG.debug("Load ClientDetails by clientId: {}", clientId); @@ -210,12 +235,12 @@ public class OauthServiceImpl implements OauthService { return oauthRepository.findAccessTokenByRefreshToken(refreshToken, clientId); } - /* - * Get AccessToken - * Generate a new AccessToken from existed(exclude token,refresh_token) - * Update access_token,refresh_token, expired. - * Save and remove old - * */ + /** + * Get AccessToken + * Generate a new AccessToken from existed(exclude token,refresh_token) + * Update access_token,refresh_token, expired. + * Save and remove old + */ @Override public AccessToken changeAccessTokenByRefreshToken(String refreshToken, String clientId) throws OAuthSystemException { final AccessToken oldToken = loadAccessTokenByRefreshToken(refreshToken, clientId); @@ -246,14 +271,22 @@ public class OauthServiceImpl implements OauthService { return clientDetails != null; } - private AccessToken createAndSaveAccessToken(ClientDetails clientDetails, boolean includeRefreshToken, String username, String authenticationId) throws OAuthSystemException { + private AccessToken createAndSaveAccessToken(ClientDetails clientDetails, boolean includeRefreshToken, String username, String authenticationId) + throws OAuthSystemException { + AccessToken accessToken = new AccessToken() .clientId(clientDetails.getClientId()) .username(username) - .tokenId(oAuthIssuer.accessToken()) +// .tokenId(tokenId) .authenticationId(authenticationId) .updateByClientDetails(clientDetails); + String tokenId = oAuthIssuer.accessToken(); + if (needEnhanceJwt()) { + tokenId = jwtTokenEnhancer.enhance(tokenId, username, clientDetails.getClientId(), accessToken.tokenExpiredSeconds()); + } + accessToken.tokenId(tokenId); + if (includeRefreshToken) { accessToken.refreshToken(oAuthIssuer.refreshToken()); } @@ -273,4 +306,15 @@ public class OauthServiceImpl implements OauthService { } + /** + * 判断是否需要增强token 用 jwt + * + * @return true yes + * @since 2.0.0 + */ + private boolean needEnhanceJwt() { + return Constants.JWT.equalsIgnoreCase(this.tokenGeneratorType); + } + + } diff --git a/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java b/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java index 8007667..77dc7ba 100644 --- a/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java +++ b/core/src/main/java/com/monkeyk/os/domain/oauth/Constants.java @@ -34,4 +34,13 @@ public interface Constants { */ String VERSION = "2.0.0"; + + /** + * Fixed keyId + * + * @since 2.0.0 + */ + String DEFAULT_KEY_ID = "oauth2-shiro-keyid"; + + } diff --git a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java index 15d61c7..bf2bbea 100644 --- a/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java +++ b/core/src/test/java/com/monkeyk/os/domain/shared/GuidGeneratorTest.java @@ -11,6 +11,16 @@ import static org.junit.jupiter.api.Assertions.*; public class GuidGeneratorTest { + @Test + void generate() { + + String uuid = GuidGenerator.generate(); + assertNotNull(uuid); +// System.out.println(uuid); + + } + + @Test void nextSaltHex() { diff --git a/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java b/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java index 8875676..b935cdd 100644 --- a/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java +++ b/core/src/test/java/com/monkeyk/os/infrastructure/Jose4JTest.java @@ -1,6 +1,8 @@ package com.monkeyk.os.infrastructure; import com.monkeyk.os.domain.shared.GuidGenerator; +import org.apache.commons.lang.RandomStringUtils; +import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwe.ContentEncryptionAlgorithmIdentifiers; import org.jose4j.jwe.JsonWebEncryption; import org.jose4j.jwe.KeyManagementAlgorithmIdentifiers; @@ -12,11 +14,14 @@ import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.keys.AesKey; import org.jose4j.keys.EllipticCurves; +import org.jose4j.keys.HmacKey; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.security.Key; +import static com.monkeyk.os.domain.oauth.Constants.DEFAULT_KEY_ID; import static org.junit.jupiter.api.Assertions.*; @@ -28,9 +33,42 @@ import static org.junit.jupiter.api.Assertions.*; public class Jose4JTest { + /** + * @throws Exception e + * @since 2.0.0 + */ + @Test + void hmacFlow() throws Exception { + + String hmacKey = "Bl0depAUL2DRPZnR0DJThK9a9KSJF4Xr"; +// System.out.println(keyStr); + + HmacKey key = new HmacKey(hmacKey.getBytes(StandardCharsets.UTF_8)); + + JsonWebSignature jws = new JsonWebSignature(); + jws.setKeyIdHeaderValue(DEFAULT_KEY_ID); + jws.setKey(key); + jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.HMAC_SHA256); + + JwtClaims claims = new JwtClaims(); +// claims.setSubject("zhangsan"); +// claims.setIssuer("https://myoidc.com"); + claims.setIssuedAtToNow(); + claims.setGeneratedJwtId(); +// claims.setExpirationTimeMinutesInTheFuture(10); + claims.setAudience("oauth2-shiro"); + jws.setPayload(claims.toJson()); + + String token = jws.getCompactSerialization(); + assertNotNull(token); +// System.out.println(token); + + } + + /* - * AES 加密与解密, 128位 - * */ + * AES 加密与解密, 128位 + * */ @Test public void aesEncryptDecrypt128() throws Exception { @@ -62,8 +100,8 @@ public class Jose4JTest { /* - * AES 加密与解密, 256位 - * */ + * AES 加密与解密, 256位 + * */ @Test public void aesEncryptDecrypt256() throws Exception { -- Gitee From 93cc15e87cd3828aa668787453a7df88a9c19444 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 27 Sep 2023 18:19:34 +0800 Subject: [PATCH 64/78] token support jwt flow --- .../java/com/monkeyk/os/service/impl/OauthServiceImpl.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java index 382b2e5..7a8875e 100644 --- a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java +++ b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java @@ -252,8 +252,12 @@ public class OauthServiceImpl implements OauthService { newAccessToken.updateByClientDetails(details); final String authId = authenticationIdGenerator.generate(clientId, oldToken.username(), null); + String tokenId = oAuthIssuer.accessToken(); + if (needEnhanceJwt()) { + tokenId = jwtTokenEnhancer.enhance(tokenId, oldToken.username(), clientId, newAccessToken.tokenExpiredSeconds()); + } newAccessToken.authenticationId(authId) - .tokenId(oAuthIssuer.accessToken()) + .tokenId(tokenId) .refreshToken(oAuthIssuer.refreshToken()); oauthRepository.deleteAccessToken(oldToken); -- Gitee From 02a4432964377f7ef22c2e1279e6e441fa34c811 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Wed, 27 Sep 2023 18:26:11 +0800 Subject: [PATCH 65/78] token support jwt flow --- .../main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java | 2 +- .../main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java | 2 +- others/database/oauth2-shiro.ddl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java b/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java index 3b4bf7d..66b3d42 100644 --- a/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java +++ b/authz/src/main/java/com/monkeyk/os/oauth/token/JwtTokenEnhancer.java @@ -38,7 +38,7 @@ public class JwtTokenEnhancer implements InitializingBean { * * @since 2.0.0 */ - @Value("${authz.token.jwt.hmac.key:Bl0depAUL2DRPZnR0DJThK9a9KSJF4Xr") + @Value("${authz.token.jwt.hmac.key:Bl0depAUL2DRPZnR0DJThK9a9KSJF4Xr}") private String jwtHmacKey; diff --git a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java index 7a8875e..799d3cc 100644 --- a/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java +++ b/authz/src/main/java/com/monkeyk/os/service/impl/OauthServiceImpl.java @@ -43,7 +43,7 @@ public class OauthServiceImpl implements OauthService { * * @since 2.0.0 */ - @Value("${authz.token.generator.type:jwt") + @Value("${authz.token.generator.type:jwt}") private String tokenGeneratorType; diff --git a/others/database/oauth2-shiro.ddl b/others/database/oauth2-shiro.ddl index ee6668b..31000e5 100644 --- a/others/database/oauth2-shiro.ddl +++ b/others/database/oauth2-shiro.ddl @@ -88,7 +88,7 @@ create table oauth_client_details ( Drop table if exists oauth_access_token; create table oauth_access_token ( create_time timestamp default now(), - token_id VARCHAR(255) unique, + token_id text, token_expired_seconds INTEGER default -1, authentication_id VARCHAR(255), username VARCHAR(255), -- Gitee From 03c587fc2cb9a07a6dd3f3a6f1f412fe330a6c4a Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 10:11:09 +0800 Subject: [PATCH 66/78] =?UTF-8?q?2.0=20API=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/html/OS_API-2.0.html | 636 ++++++++++++++++++ authz/src/main/resources/templates/login.html | 2 +- others/oauth_test.txt | 29 +- 3 files changed, 652 insertions(+), 15 deletions(-) create mode 100644 authz/src/main/resources/static/html/OS_API-2.0.html diff --git a/authz/src/main/resources/static/html/OS_API-2.0.html b/authz/src/main/resources/static/html/OS_API-2.0.html new file mode 100644 index 0000000..f9674a0 --- /dev/null +++ b/authz/src/main/resources/static/html/OS_API-2.0.html @@ -0,0 +1,636 @@ + + + + + + + + oauth2-shiro API + + + +

    + +
    + 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
    + +
    + +
    + +
    +

    [authz]

    + +

    获取access_token (grant_type=password) + public +

    + +

    使用grant_type=password方式来获取access_token

    + +
      +
    • +

      + 请求URI: /oauth/token POST +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typepassword固定值
      scope{scope}read or write
      username{username}用户名
      password{password}用户密码
      + 请求示例: +

      + http://localhost:8080/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} + +

        +
      • +
      • +

        + 异常 [400]
        + + {"error":"invalid_grant","error_description":"Bad credentials"} + +

        +
      • +
      +
    • +
    +
    + +
    +

    [authz]

    + +

    获取access_token (grant_type=authorization_code) + public +

    + +

    使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

    + +
      +
    • +

      + 请求URI: /oauth/token POST +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeauthorization_code固定值
      code{code}
      redirect_uri{redirect_uri}
      + 请求示例: +

      + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} + +

        +
      • +
      • +

        + 异常 [400]
        + + {"error":"invalid_grant","error_description":"Invalid code + '26964e42c667b5d42f89a1255766630a'"} + +

        +
      • +
      +
    • +
    +
    + +
    +

    [authz]

    + +

    获取access_token (grant_type=token) + public +

    + +

    使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

    + +
      +
    • +

      + 请求URI: /oauth/token GET +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      client_id{client_id}
      grant_typetoken固定值
      scope{scope}read or write
      redirect_uri{redirect_uri}
      + 请求示例: +

      + http://localhost:8080/authz/oauth/authorize?client_id=DYBeQ5lWFKyhY0TDSxxU&response_type=token&scope=read&redirect_uri=http://www.example.com +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + http://www.example.com#token_type=Bearer&expires_in=43199&access_token=fa3bb346a04e20431161771c0003c466 + +

        +
      • +
      +

      通过 redirect_uri的 URL hash 传递access_token信息

      +
    • +
    +
    + +
    +

    [authz]

    + +

    获取access_token (grant_type=client_credentials) + public +

    + +

    使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

    + +
      +
    • +

      + 请求URI: /oauth/token POST +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typeclient_credentials固定值
      scope{scope}read or write
      + 请求示例: +

      + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} + +

        +
      • +
      • +

        + 异常 [401]
        + + {"error":"invalid_client","error_description":"Invalid client_id + 'OMN4XjXmJidyzhUGWVrdk'"} + +

        +
      • +
      +
    • +
    +
    + +
    +

    [authz]

    + +

    刷新access_token (grant_type=refresh_token) + public +

    + +

    用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

    + +
      +
    • +

      + 请求URI: /oauth/token POST +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      client_id{client_id}
      client_secret{client_secret}
      grant_typerefresh_token固定值
      refresh_token{refresh_token}
      + 请求示例: +

      + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} + +

        +
      • +
      • +

        + 异常 [400]
        + + {"error":"invalid_grant","error_description":"Invalid refresh_token: + 8e91a56f53857688a3ffd8c7cfd311cfss"} + +

        +
      • +
      +
    • +
    +
    + +
    + +
    +

    [resources]

    + +

    获取当前系统时间(resource-id: mobile-resource)

    + +

    获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

    + +
      +
    • +

      + 请求URI: /mobile/system_time GET +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      + 请求示例: +

      + http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + {"time":1465560577614} + +

        +
      • +
      • +

        + 异常 [401]
        + + {"error":"invalid_token","error_description":"Invalid access_token: + 95c3afd44c5d87301dc3034b20b3fc75s"} + +

        +
      • +
      +
    • +
    +
    + +
    +

    [resources]

    + +

    获取当前用户信息 (resource-id: os-resource; Role: User)

    + +

    使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

    + +
      +
    • +

      + 请求URI: /rs/username GET +

      + +
      + 请求参数说明: + + + + + + + + + + + + + + +
      参数名参数值必须?备注
      + 请求示例: +

      + http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

      + +
      +
      + + 响应 + +
        +
      • +

        + 正常 [200]
        + + {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} + +

        +
      • +
      • +

        + 异常 [401]
        + + {"error":"invalid_token","error_description":"Invalid client by token: + 95c3afd44c5d87301dc3034b20b3fc75"} + +

        +
      • +
      +
    • +
    +
    + +
    +
    + + +
    +
    +
    +
    + © oauth2-shiro +
    +
    +
    + + \ No newline at end of file diff --git a/authz/src/main/resources/templates/login.html b/authz/src/main/resources/templates/login.html index e7ba867..ee911c0 100644 --- a/authz/src/main/resources/templates/login.html +++ b/authz/src/main/resources/templates/login.html @@ -105,7 +105,7 @@
  • - API + API -- oauth2-shiro提供的API文档

  • diff --git a/others/oauth_test.txt b/others/oauth_test.txt index d5ab5ae..a54d888 100644 --- a/others/oauth_test.txt +++ b/others/oauth_test.txt @@ -30,8 +30,8 @@ curl --location 'http://localhost:8080/oauth/token' \ response { - "access_token": "fab3c3a571432376d427dc402ef904ad", - "refresh_token": "8c46797a0101800626270ce6579c84fa", + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiY2I2ZmIzYTFmNzA0OGU3NDYxZjcwYWI2OGNkYTk1ZjUiLCJpYXQiOjE2OTU4NjYzODIsImV4cCI6MTY5NTkwOTU4MiwiYXVkIjoidGVzdC1jbGllbnQifQ.NqJe-j7p3UC2gJlBJ-tKB4GrFsW9OR-GyxMfm4LIfwQ", + "refresh_token": "019b043ddcf5994220617b6795c5216a", "token_type": "Bearer", "expires_in": 43199 } @@ -41,8 +41,8 @@ http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$ curl --location 'http://localhost:8080/oauth/token' \ --header 'Content-Type: application/x-www-form-urlencoded' \ ---data-urlencode 'client_id=test-client' \ ---data-urlencode 'client_secret=Test@2015$$' \ +--data-urlencode 'client_id=mobile-client' \ +--data-urlencode 'client_secret=Mobile@2015$$' \ --data-urlencode 'grant_type=password' \ --data-urlencode 'username=test' \ --data-urlencode 'password=Test@2015#' \ @@ -50,9 +50,10 @@ curl --location 'http://localhost:8080/oauth/token' \ response { - "access_token": "4bb0e34d3e517f53f0f7f3e9cb1f2c1e", + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70", + "refresh_token": "46a2017568aee3875a42f7c2234f4b3d", "token_type": "Bearer", - "expires_in": 16342 + "expires_in": 43199 } @@ -68,9 +69,9 @@ curl --location 'http://localhost:8080/oauth/token' \ response { - "access_token": "4bb0e34d3e517f53f0f7f3e9cb1f2c1e", + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWNsaWVudCIsImp0aSI6IjlkNTZhMjFhYzNhZGMzMWQyYzRjZDJlOWEyNTNkY2RmIiwiaWF0IjoxNjk1ODY2NjA5LCJleHAiOjE2OTU5MDk4MDksImF1ZCI6InRlc3QtY2xpZW50In0.brapFTd_HiPfrlKZWOK9MXOFKrDRD7v2dqXnGU7nkjI", "token_type": "Bearer", - "expires_in": 16169 + "expires_in": 43199 } -- Test grant_type='refresh_token' [POST] @@ -85,8 +86,8 @@ curl --location 'http://localhost:8080/oauth/token' \ response { - "access_token": "d64b33d5ed08e7a17d9e9e6a723b432f", - "refresh_token": "9c6d694f02d1cb472b39c91644d52f5d", + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU", + "refresh_token": "bb277d6ba38bbf5d6facae92eb29e286", "token_type": "Bearer", "expires_in": 43199 } @@ -104,7 +105,7 @@ Test-Page URL: http://localhost:8080/html/oauth_test.html http://localhost:8083/rs/username [GET] curl --location 'http://localhost:8083/rs/username' \ ---header 'Authorization: Bearer d64b33d5ed08e7a17d9e9e6a723b432f' +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU' response { @@ -132,8 +133,8 @@ curl --location 'http://localhost:8080/oauth/token' \ response { - "access_token": "ded9cdf300b8d950fa8b61a6844514aa", - "refresh_token": "f264790c4b1140ff6dfdc40b5fbd7410", + "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70", + "refresh_token": "46a2017568aee3875a42f7c2234f4b3d", "token_type": "Bearer", "expires_in": 43199 } @@ -142,7 +143,7 @@ response http://localhost:8083/mobile/system_time [GET] curl --location 'http://localhost:8083/mobile/system_time' \ ---header 'Authorization: Bearer ded9cdf300b8d950fa8b61a6844514aa' +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70' response { -- Gitee From d86cb96ca54e1cc9b26d59daaeda540ca833b8b5 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 10:30:47 +0800 Subject: [PATCH 67/78] =?UTF-8?q?2.0=20API=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/static/html/OS_API-2.0.html | 203 +++++++++--------- 1 file changed, 107 insertions(+), 96 deletions(-) diff --git a/authz/src/main/resources/static/html/OS_API-2.0.html b/authz/src/main/resources/static/html/OS_API-2.0.html index f9674a0..ad99a9f 100644 --- a/authz/src/main/resources/static/html/OS_API-2.0.html +++ b/authz/src/main/resources/static/html/OS_API-2.0.html @@ -111,9 +111,15 @@ 请求示例: -

    - http://localhost:8080/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test -

    +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=password&scope=read&username=test&password=Test@2015#
    +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=mobile-client' \
    +--data-urlencode 'client_secret=Mobile@2015$$' \
    +--data-urlencode 'grant_type=password' \
    +--data-urlencode 'username=test' \
    +--data-urlencode 'password=Test@2015#' \
    +--data-urlencode 'scope=read'

    @@ -122,20 +128,21 @@
    • -

      +

      正常 [200]
      - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} - -

      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70",
      +    "refresh_token": "46a2017568aee3875a42f7c2234f4b3d",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
    • -

      +

      异常 [400]
      - - {"error":"invalid_grant","error_description":"Bad credentials"} - -

      +
      {"error":"invalid_grant","error_description":"Bad credentials"}
      +
    @@ -203,10 +210,14 @@ 请求示例: -

    - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ -

    - +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
    +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=authorization_code' \
    +--data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \
    +--data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7'

    @@ -214,21 +225,21 @@
    • -

      +

      正常 [200]
      - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} - -

      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiY2I2ZmIzYTFmNzA0OGU3NDYxZjcwYWI2OGNkYTk1ZjUiLCJpYXQiOjE2OTU4NjYzODIsImV4cCI6MTY5NTkwOTU4MiwiYXVkIjoidGVzdC1jbGllbnQifQ.NqJe-j7p3UC2gJlBJ-tKB4GrFsW9OR-GyxMfm4LIfwQ",
      +    "refresh_token": "019b043ddcf5994220617b6795c5216a",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
    • -

      +

      异常 [400]
      - - {"error":"invalid_grant","error_description":"Invalid code - '26964e42c667b5d42f89a1255766630a'"} - -

      +
      {"error":"invalid_grant","error_description":"Invalid code '26964e42c667b5d42f89a1255766630a'"}
      +
    @@ -248,7 +259,8 @@
  • 请求URI: /oauth/token GET + class="label label-info">GET [deprecated]

    @@ -290,9 +302,7 @@ 请求示例: -

    - http://localhost:8080/authz/oauth/authorize?client_id=DYBeQ5lWFKyhY0TDSxxU&response_type=token&scope=read&redirect_uri=http://www.example.com -

    +
    http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback

    @@ -301,12 +311,10 @@
    • -

      +

      正常 [200]
      - - http://www.example.com#token_type=Bearer&expires_in=43199&access_token=fa3bb346a04e20431161771c0003c466 - -

      +
      http://localhost:7777/spring-oauth-client/authorization_code_callback#access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZmI2YWM1M2E5YjRlMjFkMjcyMmU4Y2FjMDBjODkyNGUiLCJpYXQiOjE2OTU4Njc3MjMsImV4cCI6MTY5NTkxMDkyMywiYXVkIjoidGVzdC1jbGllbnQifQ.i7WyVE_08DeKeq_SpI-C2sqTaKDXt-wKck1L_L_aW98&token_type=Bearer&expires_in=43199
      +

    通过 redirect_uri的 URL hash 传递access_token信息

    @@ -370,10 +378,14 @@ 请求示例: -

    - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read -

    - +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=client_credentials&scope=read
    + +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=client_credentials' \
    +--data-urlencode 'scope=read'

  • @@ -381,21 +393,20 @@
    • -

      +

      正常 [200]
      - - {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} - -

      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWNsaWVudCIsImp0aSI6IjlkNTZhMjFhYzNhZGMzMWQyYzRjZDJlOWEyNTNkY2RmIiwiaWF0IjoxNjk1ODY2NjA5LCJleHAiOjE2OTU5MDk4MDksImF1ZCI6InRlc3QtY2xpZW50In0.brapFTd_HiPfrlKZWOK9MXOFKrDRD7v2dqXnGU7nkjI",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
    • -

      +

      异常 [401]
      - - {"error":"invalid_client","error_description":"Invalid client_id - 'OMN4XjXmJidyzhUGWVrdk'"} - -

      +
      {"error":"invalid_client","error_description":"Invalid client_id'test-xxx'"}
      +
    @@ -457,10 +468,14 @@ 请求示例: -

    - http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf -

    - +
    http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1
    + +
    curl --location 'http://localhost:8080/oauth/token' \
    +--header 'Content-Type: application/x-www-form-urlencoded' \
    +--data-urlencode 'client_id=test-client' \
    +--data-urlencode 'client_secret=Test@2015$$' \
    +--data-urlencode 'grant_type=refresh_token' \
    +--data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa'

    @@ -468,21 +483,22 @@
    • -

      +

      正常 [200]
      - - {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} - -

      +
      {
      +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU",
      +    "refresh_token": "bb277d6ba38bbf5d6facae92eb29e286",
      +    "token_type": "Bearer",
      +    "expires_in": 43199
      +}
      +
    • -

      +

      异常 [400]
      - - {"error":"invalid_grant","error_description":"Invalid refresh_token: - 8e91a56f53857688a3ffd8c7cfd311cfss"} - -

      +
      {"error":"invalid_grant","error_description":"Invalid refresh_token: 8e91a56f53857688a3ffd8c7cfd311cfss"}
      +                                
      +
    @@ -523,9 +539,9 @@ 请求示例: -

    - http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

    +
    curl --location 'http://localhost:8083/mobile/system_time' \
    +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70'
    +

    @@ -534,21 +550,18 @@
    • -

      +

      正常 [200]
      - - {"time":1465560577614} - -

      +
      {
      +    "time": 1695628213913
      +}
      +
    • -

      +

      异常 [401]
      - - {"error":"invalid_token","error_description":"Invalid access_token: - 95c3afd44c5d87301dc3034b20b3fc75s"} - -

      +
      {"error":"invalid_token","error_description":"Invalid access_token: 95c3afd44c5d87301dc3034b20b3fc75s"}
      +
    @@ -588,9 +601,9 @@ 请求示例: -

    - http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 -

    +
    curl --location 'http://localhost:8083/rs/username' \
    +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU'
    +

    @@ -599,21 +612,19 @@
    • -

      +

      正常 [200]
      - - {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} - -

      +
      {
      +    "clientId": "test-client",
      +    "username": "test"
      +}
      +
    • -

      +

      异常 [401]
      - - {"error":"invalid_token","error_description":"Invalid client by token: - 95c3afd44c5d87301dc3034b20b3fc75"} - -

      +
      {"error":"invalid_token","error_description":"Invalid client by token: 95c3afd44c5d87301dc3034b20b3fc75"}
      +
    @@ -628,7 +639,7 @@

    - © oauth2-shiro + © 2015-2023 oauth2-shiro
    -- Gitee From 380c9f9b6e094802f16b15e4d3ca9d3a98346e74 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 10:34:32 +0800 Subject: [PATCH 68/78] =?UTF-8?q?2.0=20API=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- authz/src/main/resources/static/html/OS_API-2.0.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/authz/src/main/resources/static/html/OS_API-2.0.html b/authz/src/main/resources/static/html/OS_API-2.0.html index ad99a9f..3a77b53 100644 --- a/authz/src/main/resources/static/html/OS_API-2.0.html +++ b/authz/src/main/resources/static/html/OS_API-2.0.html @@ -496,8 +496,7 @@
  • 异常 [400]
    -
    {"error":"invalid_grant","error_description":"Invalid refresh_token: 8e91a56f53857688a3ffd8c7cfd311cfss"}
    -                                
    +
    {"error":"invalid_grant","error_description":"Invalid refresh_token: 8e91a56f53857688a3ffd8c7cfd311cfss"}
  • @@ -539,6 +538,8 @@ 请求示例: +
    http://localhost:8083/mobile/system_time?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOT...
    +
    curl --location 'http://localhost:8083/mobile/system_time' \
     --header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70'
     
    @@ -601,6 +602,8 @@ 请求示例: +
    http://localhost:8083/rs/username?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIi...
    +
    curl --location 'http://localhost:8083/rs/username' \
     --header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU'
     
    -- Gitee From aacf903a01726da0eba023b7174da618bcfbf512 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 10:51:56 +0800 Subject: [PATCH 69/78] API test --- authz/src/main/resources/templates/oauth/test_client.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/authz/src/main/resources/templates/oauth/test_client.html b/authz/src/main/resources/templates/oauth/test_client.html index e518b5a..e57942f 100644 --- a/authz/src/main/resources/templates/oauth/test_client.html +++ b/authz/src/main/resources/templates/oauth/test_client.html @@ -113,8 +113,6 @@ required="required"/>
    - -
    @@ -140,7 +138,7 @@ - +
    @@ -39,7 +38,7 @@
    • JDK -- 1.8.0_40

    • Maven -- 3.6.0

    • -
    • MySQL -- 5.6.23-log

    • +
    • MySQL -- 5.7.22


    @@ -49,7 +48,7 @@
    • authz 实现使用各类grant_type去获取token业务逻辑----获取access_token

    • core 将公共部分提取到该模块中, 减少重复代码, 保证一致性, 如定义ClientDetails, AccessToken; authz, resources 模块都依赖于该模块

    • -
    • resources 资源管理模块,将受OAUTH保护的资源(URI)放在这里----使用access_token

    • +
    • resources 资源管理模块,将受OAuth2保护的资源(URI)放在这里----使用access_token

    @@ -65,9 +64,10 @@

    Redis版本

    + Redis版本的实现主要是将Token数据存储换成Redis,并利用TTL特性自动清除过期数据,性能更高。
    -- Gitee From 59b0e86788409990ef4912ce659366d7d4d2683b Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 11:28:41 +0800 Subject: [PATCH 71/78] Update README.md for v2.0 --- README.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c6c9527..ac7653c 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@

    如何使用

    1. - 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.6.0), 还有MySql(开发用的mysql版本号为5.6) + 项目是Maven管理的, 需要本地安装maven(开发用的maven版本号为3.6.0), 数据库MySql(开发用的mysql版本号为5.7.22)
    2. 下载(或clone)项目到本地 @@ -84,7 +84,7 @@ 项目由三个模块(core,authz,resources)组成, core是一个Java项目(jar), authz与resources是SpringBoot项目(.jar)
    3. - 创建MySQL数据库(如数据库名 oauth2_shiro), 并运行相应的SQL脚本(脚本文件位于others/database目录), + 创建MySQL数据库(数据库名 oauth2_shiro), 并运行相应的SQL脚本(脚本文件位于others/database目录),
      运行脚本的顺序: oauth2-shiro.ddl -> initial-db.ddl
    4. @@ -93,21 +93,23 @@ 修改配置文件中的数据库连接信息(包括username, password等), 都连接到数据库oauth2_shiro
    5. -将本地项目导入到IDE(如Intellij IDEA)中, 并启动(authz默认端口为8080,启动类 AuthzApplication.java ); +将本地项目导入到IDE(如Intellij IDEA)中, 并启动服务。
      - +(authz默认端口为8080,启动类 AuthzApplication.java; resources默认端口为8083,启动类 ResourcesApplication.java )。 +
      +启动后查看控制台的日志输出或查看logs/目录中按日期分类的log日志。
      - 另: 也可通过maven package命令将项目编译为war文件(os.war), 注意编译时每个模块的pom.xml文件中配置的数据库连接信息, 可在Maven命令中添加 -Dmaven.test.skip=true 忽略测试; - 将authz模块与resources模块生成的war放在Tomcat中并启动(注意: 这种方式需要将 authz.properties与resources.properties 加入到classpath中并正确配置数据库连接信息). + 另: 也可通过maven package命令将项目编译为SpringBoot jar文件(authz.jar 与 rs.jar), 注意编译时每个模块的pom.xml文件中配置的数据库连接信息, 可在Maven命令中添加 -Dmaven.test.skip=true 忽略单元测试; + 将authz模块与resources模块生成的jar通过 java -jar 命令启动( java -jar authz.jarjava -jar rs.jar)。
    6. - 参考oauth_test.txt(位于others目录)的内容并测试之(也可在浏览器中访问相应的地址,如: http://localhost:8080/os/). + 在浏览器中直接访问authz地址: http://localhost:8080 在线测试或 + 参考oauth_test.txt(位于others目录)的内容使用postman等工具测试。
    -
    支持的 grant_type
    -- Gitee From 1af38c698e6de0d37778c6f21b390e167a9a557f Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 11:36:41 +0800 Subject: [PATCH 72/78] Update README.md for v2.0 --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ac7653c..13cf267 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,9 @@ 创建MySQL数据库(数据库名 oauth2_shiro), 并运行相应的SQL脚本(脚本文件位于others/database目录),
    运行脚本的顺序: oauth2-shiro.ddl -> initial-db.ddl +
    + 若需要运行单元测试,则还需要创建数据库 oauth2_shiro_test 并运行SQL脚本 +(单元测试的配置文件为application-test.properties,位于 src/test/resources 目录中)。
  • 依次修改authz模块的配置文件application.properties(位于模块的src/main/resources目录)与resources模块的配置文件application.properties(位于模块的src/main/resources目录); @@ -111,17 +114,18 @@
    -支持的 grant_type -
    -说明 oauth2-shiro 项目支持的grant_type(授权方式)与功能 +

    支持的 grant_type

    + +说明 oauth2-shiro 项目支持的OAuth2 grant_type (授权方式)与功能
    1. authorization_code -- 授权码模式(即先登录获取code,再获取token)
    2. -
    3. password -- 密码模式(将用户名,密码传过去,直接获取token)
    4. +
    5. password -- 密码模式(将用户名,密码传过去,直接获取token) [不推荐]
    6. refresh_token -- 刷新access_token
    7. -
    8. implicit(token) -- 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash)
    9. +
    10. implicit(token) -- 简化模式(在redirect_uri 的Hash传递token; Auth客户端运行在浏览器中,如JS,Flash) [不推荐]
    11. client_credentials -- 客户端模式(无用户,用户向客户端注册,然后客户端以自己的名义向'服务端'获取资源)
    - +在OAuth2.1中对 grant_type的使用进行了安全升级(如不推荐使用 password 方式), +详细变化请查看 OAuth2.1的状态与主要特征
    -- Gitee From 955e1442ce52cae02913a6706f7a629175357edd Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 11:48:20 +0800 Subject: [PATCH 73/78] Update README.md for v2.0 --- README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 13cf267..80649a2 100644 --- a/README.md +++ b/README.md @@ -141,15 +141,28 @@
    • - Version: 2.0.0 [pending] + Version: 2.0.1 [plan]
      - Date: 2020-07-05 / --- + Date: --- / ---

        -
      1. 升级使用Spring Boot

      2. -
      3. 升级使用JDK 1.8, 日志框架升级使用logback

      4. -
      5. (153) - 尝试添加并实现OIDC在 oauth2-shiro中

      6. -
      7. 升级shiro到1.5.3, oltu版本无变化

      8. +
      9. ---

      10. +
      +
      +
    • +
    • +

      + Version: 2.0.0 [finished] +
      + Date: 2020-07-05 / 2022-09-28 +

      +
        +
      1. 升级使用Spring Boot,调整工程结构,使用 thymeleaf替换 servlet/jsp,打包由war换成jar

      2. +
      3. 升级使用JDK 1.8, 日志框架使用logback替换log4j(处理log4j的安全漏洞)

      4. +
      5. token支持使用JWT格式,通过配置参数authz.token.generator.type来控制与向下兼容

      6. +
      7. 升级shiro到v1.11.0

      8. +
      9. 密码存储算法由MD5换成SHA-256,并支持盐(salt),使密码存储更安全可靠;通过配置参数authz.store.credentials.alg来控制与向下兼容

      10. +
      11. 对初始的账户密码使用更安全的密码策略:包括大小写字母,数字与特殊符号,长度至少10位


    • -- Gitee From 74f4f80262206acad75ba1ffcbe426180cb26b60 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 12:01:42 +0800 Subject: [PATCH 74/78] Update README.md for v2.0 --- README.md | 79 ++++++++++++++++++++++--------------------------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 80649a2..9124b30 100644 --- a/README.md +++ b/README.md @@ -199,37 +199,24 @@
    -
    -Project Log -

    + +

    +

    项目动态

    1. 2015-05-17 Initial project, start push code (private)

    2. 2015-07-16 oauth2-shiro项目开发状态(7月)

    3. 2015-09-06 oauth2-shiro项目开发状态(8月)

    4. 2015-09-06 项目由 私有 变为 开源, 开发 resource 模块

    5. -
    6. 2015-09-26 版本0.1 开发完毕, 发布 0.1-beta 版本

    7. -
    8. 2015-10-07 重构项目结构, 发布 0.1-rc 版本

    9. +
    10. 2015-09-26 版本0.1 开发完毕, 发布 0.1-beta 版本

    11. +
    12. 2015-10-07 重构项目结构, 发布 0.1-rc 版本

    13. 2016-05-26 开始开发 0.2 版本

    14. 2016-07-02 添加在线测试环境

    15. -
    16. 2016-08-17 发布 0.2 版本

    17. +
    18. 2016-08-17 发布 0.2 版本

    19. 2017-01-21 加入到GitHub中, Git@OSC地址: http://git.oschina.net/mkk/oauth2-shiro

    20. 2020-07-05 开始2.0.0版本开发

    21. +
    22. 2023-09-28 发布 2.0.0 版本,大升级

    -

    - - - -
    - -
    @@ -242,38 +229,36 @@
    -
    -

    - 与Oauth2相关的技术文章请访问 http://andaily.com/blog/?cat=19 (不断更新与Oauth相关的文章) -

    -

    - 问答与讨论 -
    - 与项目相关的,与Oauth相关的问题与回答,以及各类讨论请访问
    - http://andaily.com/blog/?dwqa-question_category=oauth -

    +

    问答与讨论

    + + 与OAuth2相关的技术文章请访问 http://andaily.com/blog/?cat=19 (不断更新与OAuth相关的文章) + +
    + 与项目相关的,与OAuth相关的问题与回答,以及各类讨论请访问
    + http://andaily.com/blog/?dwqa-question_category=oauth +
    -

    - 捐助 -
    - 支付宝: monkeyking1987@126.com (**钊) -
    - 明瑞 -- 5元 -
    - Triton -- 8.8元 -
    -    半个鼠标 -- 10元 -
    - 张宏俊 -- 20元 (2018-03-28) -

    + +

    捐助历史

    +
    +支付宝: monkeyking1987@126.com (**钊) +
    +明瑞 -- 5元 +
    +Triton -- 8.8元 +
    +半个鼠标 -- 10元 +
    +张宏俊 -- 20元 (2018-03-28)
    +

    开源扩展

    - 关注更多我的开源项目请访问 http://andaily.com/my_projects.html + 关注更多我的开源项目请访问 https://andaily.com/my_projects.html

    -

    - 若需更多的商业技术支持请联系 sz@qc8.com -

    + + 若需更多的商业技术支持请联系 monkeyk1987@gmail.com或发私信。 + -- Gitee From 5c42b28fe6144b842d2e225b7cb79d2bc829933c Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 12:04:14 +0800 Subject: [PATCH 75/78] Update README.md for v2.0 --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9124b30..656ae84 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,8 @@

    姊妹项目

    @@ -232,26 +232,25 @@

    问答与讨论

    - 与OAuth2相关的技术文章请访问 http://andaily.com/blog/?cat=19 (不断更新与OAuth相关的文章) + 与OAuth2相关的技术文章请访问 https://andaily.com/blog/?cat=19 (不断更新与OAuth相关的文章)
    与项目相关的,与OAuth相关的问题与回答,以及各类讨论请访问
    - http://andaily.com/blog/?dwqa-question_category=oauth + https://andaily.com/blog/?dwqa-question_category=oauth

    捐助历史

    -
    + 支付宝: monkeyking1987@126.com (**钊)
    -明瑞 -- 5元 -
    -Triton -- 8.8元 -
    -半个鼠标 -- 10元 -
    -张宏俊 -- 20元 (2018-03-28) +
      +
    • 明瑞 -- 5元
    • +
    • Triton -- 8.8元
    • +
    • 半个鼠标 -- 10元
    • +
    • 张宏俊 -- 20元 (2018-03-28)
    • +

    开源扩展

    -- Gitee From 1b959b74734fc306552d65e49c1919e3581f86d4 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 12:17:13 +0800 Subject: [PATCH 76/78] Update README.md for v2.0 --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 656ae84..3edffe6 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@

    在线测试

    @@ -154,7 +154,7 @@

    Version: 2.0.0 [finished]
    - Date: 2020-07-05 / 2022-09-28 + Date: 2020-07-05 / 2023-09-28

    1. 升级使用Spring Boot,调整工程结构,使用 thymeleaf替换 servlet/jsp,打包由war换成jar

    2. @@ -241,7 +241,7 @@
      -

      捐助历史

      +

      捐赠历史

      支付宝: monkeyking1987@126.com (**钊)
      @@ -249,6 +249,11 @@
    3. 明瑞 -- 5元
    4. Triton -- 8.8元
    5. 半个鼠标 -- 10元
    6. +
    7. Andis -- 10元 (2017-03-09)
    8. +
    9. Max Shu -- 10元 (2017-04-13)
    10. +
    11. 514407363 -- 6.66元 (2017-06-02)
    12. +
    13. 自负 -- 5元 (2017-09-18)
    14. +
    15. 红领巾 -- 10元 (2017-09-28)
    16. 张宏俊 -- 20元 (2018-03-28)
    17. -- Gitee From 3dd0561809f27f1946d0913f63f1cca81dac4a83 Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 12:18:05 +0800 Subject: [PATCH 77/78] Update README.md for v2.0 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3edffe6..559f0bc 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@

      在线测试

      -- Gitee From 8859e12bc4f574e529e913179fbdd9422c1449bd Mon Sep 17 00:00:00 2001 From: "shengzhaoli.shengz" Date: Thu, 28 Sep 2023 12:33:21 +0800 Subject: [PATCH 78/78] RS welcome.html --- resources/pom.xml | 5 + .../os/web/controller/WelcomeController.java | 10 +- .../resources/static/css/bootstrap.min.css | 5 + .../resources/static/html/OS_API-0.2.html | 555 +++++++++++++++ .../resources/static/html/OS_API-0.3.html | 636 +++++++++++++++++ .../resources/static/html/OS_API-2.0.html | 650 ++++++++++++++++++ .../resources/templates/fragments/main.html | 32 + .../src/main/resources/templates/welcome.html | 60 ++ 8 files changed, 1949 insertions(+), 4 deletions(-) create mode 100644 resources/src/main/resources/static/css/bootstrap.min.css create mode 100644 resources/src/main/resources/static/html/OS_API-0.2.html create mode 100644 resources/src/main/resources/static/html/OS_API-0.3.html create mode 100644 resources/src/main/resources/static/html/OS_API-2.0.html create mode 100644 resources/src/main/resources/templates/fragments/main.html create mode 100644 resources/src/main/resources/templates/welcome.html diff --git a/resources/pom.xml b/resources/pom.xml index 0de0cb2..45bdbcd 100644 --- a/resources/pom.xml +++ b/resources/pom.xml @@ -66,6 +66,11 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-thymeleaf + + mysql mysql-connector-java diff --git a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java index 6b9d87f..a6699e4 100644 --- a/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java +++ b/resources/src/main/java/com/monkeyk/os/web/controller/WelcomeController.java @@ -1,7 +1,8 @@ package com.monkeyk.os.web.controller; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; import static com.monkeyk.os.domain.oauth.Constants.VERSION; @@ -11,13 +12,14 @@ import static com.monkeyk.os.domain.oauth.Constants.VERSION; * @author Shengzhao Li * @since 2.0.0 */ -@RestController +@Controller public class WelcomeController { @GetMapping("/") - public String welcome() { - return "RS is working... v" + VERSION; + public String welcome(Model model) { + model.addAttribute("version", VERSION); + return "welcome"; } } diff --git a/resources/src/main/resources/static/css/bootstrap.min.css b/resources/src/main/resources/static/css/bootstrap.min.css new file mode 100644 index 0000000..cd1c616 --- /dev/null +++ b/resources/src/main/resources/static/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px)and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px)and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px)and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px)and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px)and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/resources/src/main/resources/static/html/OS_API-0.2.html b/resources/src/main/resources/static/html/OS_API-0.2.html new file mode 100644 index 0000000..3b8cfd5 --- /dev/null +++ b/resources/src/main/resources/static/html/OS_API-0.2.html @@ -0,0 +1,555 @@ + + + + + + + + oauth2-shiro API + + + + + +
      + 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
      + +
      + +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=password) + public +

      + +

      使用grant_type=password方式来获取access_token

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typepassword固定值
        scope{scope}read or write
        username{username}用户名
        password{password}用户密码
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} + +

          +
        • +
        • +

          + 异常 [400]
          + + {"error":"invalid_grant","error_description":"Bad credentials"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=authorization_code) + public +

      + +

      使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typeauthorization_code固定值
        code{code}
        redirect_uri{redirect_uri}
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} + +

          +
        • +
        • +

          + 异常 [400]
          + + {"error":"invalid_grant","error_description":"Invalid code + '26964e42c667b5d42f89a1255766630a'"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=client_credentials) + public +

      + +

      使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typeclient_credentials固定值
        scope{scope}read or write
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} + +

          +
        • +
        • +

          + 异常 [401]
          + + {"error":"invalid_client","error_description":"Invalid client_id + 'OMN4XjXmJidyzhUGWVrdk'"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      刷新access_token (grant_type=refresh_token) + public +

      + +

      用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typerefresh_token固定值
        refresh_token{refresh_token}
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} + +

          +
        • +
        • +

          + 异常 [400]
          + + {"error":"invalid_grant","error_description":"Invalid refresh_token: + 8e91a56f53857688a3ffd8c7cfd311cfss"} + +

          +
        • +
        +
      • +
      +
      + +
      + +
      +

      [resources]

      + +

      获取当前系统时间(resource-id: mobile-resource)

      + +

      获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

      + +
        +
      • +

        + 请求URI: /mobile/system_time GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        + 请求示例: +

        + http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"time":1465560577614} + +

          +
        • +
        • +

          + 异常 [401]
          + + {"error":"invalid_token","error_description":"Invalid access_token: + 95c3afd44c5d87301dc3034b20b3fc75s"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [resources]

      + +

      获取当前用户信息 (resource-id: os-resource; Role: User)

      + +

      使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

      + +
        +
      • +

        + 请求URI: /rs/username GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        + 请求示例: +

        + http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} + +

          +
        • +
        • +

          + 异常 [401]
          + + {"error":"invalid_token","error_description":"Invalid client by token: + 95c3afd44c5d87301dc3034b20b3fc75"} + +

          +
        • +
        +
      • +
      +
      + +
      +
      + + +
      +
      +
      +
      + © oauth2-shiro +
      +
      +
      + + \ No newline at end of file diff --git a/resources/src/main/resources/static/html/OS_API-0.3.html b/resources/src/main/resources/static/html/OS_API-0.3.html new file mode 100644 index 0000000..de70bb2 --- /dev/null +++ b/resources/src/main/resources/static/html/OS_API-0.3.html @@ -0,0 +1,636 @@ + + + + + + + + oauth2-shiro API + + + + + +
      + 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
      + +
      + +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=password) + public +

      + +

      使用grant_type=password方式来获取access_token

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typepassword固定值
        scope{scope}read or write
        username{username}用户名
        password{password}用户密码
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=password&scope=read&username=test&password=test +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a135278d0382260ab9afaea05e5cbb26","access_token":"81fab07a5c91bcd06f60419fb22ecc9f"} + +

          +
        • +
        • +

          + 异常 [400]
          + + {"error":"invalid_grant","error_description":"Bad credentials"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=authorization_code) + public +

      + +

      使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typeauthorization_code固定值
        code{code}
        redirect_uri{redirect_uri}
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=authorization_code&code=26964a1255766630a&redirect_uri=http://localhost:8080/authz/ +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"8e91a56f53857688a3ffd8c7cfd311cf","access_token":"8bdaab126137049bd209631a23024f12"} + +

          +
        • +
        • +

          + 异常 [400]
          + + {"error":"invalid_grant","error_description":"Invalid code + '26964e42c667b5d42f89a1255766630a'"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=token) + public +

      + +

      使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

      + +
        +
      • +

        + 请求URI: /oauth/token GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        grant_typetoken固定值
        scope{scope}read or write
        redirect_uri{redirect_uri}
        + 请求示例: +

        + http://localhost:8080/authz/oauth/authorize?client_id=DYBeQ5lWFKyhY0TDSxxU&response_type=token&scope=read&redirect_uri=http://www.example.com +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + http://www.example.com#token_type=Bearer&expires_in=43199&access_token=fa3bb346a04e20431161771c0003c466 + +

          +
        • +
        +

        通过 redirect_uri的 URL hash 传递access_token信息

        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=client_credentials) + public +

      + +

      使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typeclient_credentials固定值
        scope{scope}read or write
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=client_credentials&scope=read +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":19476,"access_token":"ee7c7d1bf0cea77a883a082cb7085b64"} + +

          +
        • +
        • +

          + 异常 [401]
          + + {"error":"invalid_client","error_description":"Invalid client_id + 'OMN4XjXmJidyzhUGWVrdk'"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      刷新access_token (grant_type=refresh_token) + public +

      + +

      用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typerefresh_token固定值
        refresh_token{refresh_token}
        + 请求示例: +

        + http://localhost:8080/authz/oauth/token?client_id=OMN4XjXmJidyzhUGWVrk&client_secret=wzRBGCblLSD4Zzfb3gl3&grant_type=refresh_token&refresh_token=8e91a56f53857688a3ffd8c7cfd311cf +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"token_type":"Bearer","expires_in":43199,"refresh_token":"a407f77b8269493433e0756aedabad66","access_token":"a9beb6b987b3365f9c2efc46e19b1f1a"} + +

          +
        • +
        • +

          + 异常 [400]
          + + {"error":"invalid_grant","error_description":"Invalid refresh_token: + 8e91a56f53857688a3ffd8c7cfd311cfss"} + +

          +
        • +
        +
      • +
      +
      + +
      + +
      +

      [resources]

      + +

      获取当前系统时间(resource-id: mobile-resource)

      + +

      获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

      + +
        +
      • +

        + 请求URI: /mobile/system_time GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        + 请求示例: +

        + http://localhost:8080/rs/mobile/system_time?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"time":1465560577614} + +

          +
        • +
        • +

          + 异常 [401]
          + + {"error":"invalid_token","error_description":"Invalid access_token: + 95c3afd44c5d87301dc3034b20b3fc75s"} + +

          +
        • +
        +
      • +
      +
      + +
      +

      [resources]

      + +

      获取当前用户信息 (resource-id: os-resource; Role: User)

      + +

      使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

      + +
        +
      • +

        + 请求URI: /rs/username GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        + 请求示例: +

        + http://localhost:8080/rs/rs/username?access_token=95c3afd44c5d87301dc3034b20b3fc75 +

        + +
        +
        + + 响应 + +
          +
        • +

          + 正常 [200]
          + + {"clientId":"WQlJ2ZZBV8iJGKnkqfdbgvfVgY3Cp17AEbMijnID","username":"xiaowang"} + +

          +
        • +
        • +

          + 异常 [401]
          + + {"error":"invalid_token","error_description":"Invalid client by token: + 95c3afd44c5d87301dc3034b20b3fc75"} + +

          +
        • +
        +
      • +
      +
      + +
      +
      + + +
      +
      +
      +
      + © oauth2-shiro +
      +
      +
      + + \ No newline at end of file diff --git a/resources/src/main/resources/static/html/OS_API-2.0.html b/resources/src/main/resources/static/html/OS_API-2.0.html new file mode 100644 index 0000000..3a77b53 --- /dev/null +++ b/resources/src/main/resources/static/html/OS_API-2.0.html @@ -0,0 +1,650 @@ + + + + + + + + oauth2-shiro API + + + + + +
      + 说明: 本文档用于描述oauth2-shiro对外开发的接口(API)使用,分为 authz 与 resources 两个部分, 所有标记 + public + 的API都是公开的, 其他的API则需要获取 + access_token + 后可调用 +
      + +
      + +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=password) + public +

      + +

      使用grant_type=password方式来获取access_token

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typepassword固定值
        scope{scope}read or write
        username{username}用户名
        password{password}用户密码
        + 请求示例: +
        http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=password&scope=read&username=test&password=Test@2015#
        +
        curl --location 'http://localhost:8080/oauth/token' \
        +--header 'Content-Type: application/x-www-form-urlencoded' \
        +--data-urlencode 'client_id=mobile-client' \
        +--data-urlencode 'client_secret=Mobile@2015$$' \
        +--data-urlencode 'grant_type=password' \
        +--data-urlencode 'username=test' \
        +--data-urlencode 'password=Test@2015#' \
        +--data-urlencode 'scope=read'
        + +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          {
          +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70",
          +    "refresh_token": "46a2017568aee3875a42f7c2234f4b3d",
          +    "token_type": "Bearer",
          +    "expires_in": 43199
          +}
          +
          +
        • +
        • +
          + 异常 [400]
          +
          {"error":"invalid_grant","error_description":"Bad credentials"}
          +
          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=authorization_code) + public +

      + +

      使用grant_type=authorization_code 方式来获取access_token, 需要先获取code

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typeauthorization_code固定值
        code{code}
        redirect_uri{redirect_uri}
        + 请求示例: +
        http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=authorization_code&code=ac0bd18863b07adfb518cc6e6dfcfcab&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
        +
        curl --location 'http://localhost:8080/oauth/token' \
        +--header 'Content-Type: application/x-www-form-urlencoded' \
        +--data-urlencode 'client_id=test-client' \
        +--data-urlencode 'client_secret=Test@2015$$' \
        +--data-urlencode 'grant_type=authorization_code' \
        +--data-urlencode 'redirect_uri=http://localhost:7777/spring-oauth-client/authorization_code_callback' \
        +--data-urlencode 'code=52aa9d9cb8e62649e887e745fda94fa7'
        +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          {
          +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiY2I2ZmIzYTFmNzA0OGU3NDYxZjcwYWI2OGNkYTk1ZjUiLCJpYXQiOjE2OTU4NjYzODIsImV4cCI6MTY5NTkwOTU4MiwiYXVkIjoidGVzdC1jbGllbnQifQ.NqJe-j7p3UC2gJlBJ-tKB4GrFsW9OR-GyxMfm4LIfwQ",
          +    "refresh_token": "019b043ddcf5994220617b6795c5216a",
          +    "token_type": "Bearer",
          +    "expires_in": 43199
          +}
          +
          +
        • +
        • +
          + 异常 [400]
          +
          {"error":"invalid_grant","error_description":"Invalid code '26964e42c667b5d42f89a1255766630a'"}
          +
          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=token) + public +

      + +

      使用grant_type=token 方式来获取access_token, implicit模式; 需要登录

      + +
        +
      • +

        + 请求URI: /oauth/token GET [deprecated] +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        grant_typetoken固定值
        scope{scope}read or write
        redirect_uri{redirect_uri}
        + 请求示例: +
        http://localhost:8080/oauth/authorize?response_type=token&scope=read write&client_id=test-client&redirect_uri=http%3A%2F%2Flocalhost%3A7777%2Fspring-oauth-client%2Fauthorization_code_callback
        + +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          http://localhost:7777/spring-oauth-client/authorization_code_callback#access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZmI2YWM1M2E5YjRlMjFkMjcyMmU4Y2FjMDBjODkyNGUiLCJpYXQiOjE2OTU4Njc3MjMsImV4cCI6MTY5NTkxMDkyMywiYXVkIjoidGVzdC1jbGllbnQifQ.i7WyVE_08DeKeq_SpI-C2sqTaKDXt-wKck1L_L_aW98&token_type=Bearer&expires_in=43199
          +
          +
        • +
        +

        通过 redirect_uri的 URL hash 传递access_token信息

        +
      • +
      +
      + +
      +

      [authz]

      + +

      获取access_token (grant_type=client_credentials) + public +

      + +

      使用grant_type=client_credentials 方式来获取access_token, 不需要username, password, 不支持 + refresh_token

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typeclient_credentials固定值
        scope{scope}read or write
        + 请求示例: +
        http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=client_credentials&scope=read
        + +
        curl --location 'http://localhost:8080/oauth/token' \
        +--header 'Content-Type: application/x-www-form-urlencoded' \
        +--data-urlencode 'client_id=test-client' \
        +--data-urlencode 'client_secret=Test@2015$$' \
        +--data-urlencode 'grant_type=client_credentials' \
        +--data-urlencode 'scope=read'
        +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          {
          +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0LWNsaWVudCIsImp0aSI6IjlkNTZhMjFhYzNhZGMzMWQyYzRjZDJlOWEyNTNkY2RmIiwiaWF0IjoxNjk1ODY2NjA5LCJleHAiOjE2OTU5MDk4MDksImF1ZCI6InRlc3QtY2xpZW50In0.brapFTd_HiPfrlKZWOK9MXOFKrDRD7v2dqXnGU7nkjI",
          +    "token_type": "Bearer",
          +    "expires_in": 43199
          +}
          +
          +
        • +
        • +
          + 异常 [401]
          +
          {"error":"invalid_client","error_description":"Invalid client_id'test-xxx'"}
          +
          +
        • +
        +
      • +
      +
      + +
      +

      [authz]

      + +

      刷新access_token (grant_type=refresh_token) + public +

      + +

      用于在access_token要过期时换取新的access_token (grant_type需要有refresh_token)

      + +
        +
      • +

        + 请求URI: /oauth/token POST +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        client_id{client_id}
        client_secret{client_secret}
        grant_typerefresh_token固定值
        refresh_token{refresh_token}
        + 请求示例: +
        http://localhost:8080/oauth/token?client_id=test-client&client_secret=Test@2015$$&grant_type=refresh_token&refresh_token=b36f4978a1724aa8af8960f58abe3ba1
        + +
        curl --location 'http://localhost:8080/oauth/token' \
        +--header 'Content-Type: application/x-www-form-urlencoded' \
        +--data-urlencode 'client_id=test-client' \
        +--data-urlencode 'client_secret=Test@2015$$' \
        +--data-urlencode 'grant_type=refresh_token' \
        +--data-urlencode 'refresh_token=8c46797a0101800626270ce6579c84fa'
        +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          {
          +    "access_token": "eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU",
          +    "refresh_token": "bb277d6ba38bbf5d6facae92eb29e286",
          +    "token_type": "Bearer",
          +    "expires_in": 43199
          +}
          +
          +
        • +
        • +
          + 异常 [400]
          +
          {"error":"invalid_grant","error_description":"Invalid refresh_token: 8e91a56f53857688a3ffd8c7cfd311cfss"}
          +
          +
        • +
        +
      • +
      +
      + +
      + +
      +

      [resources]

      + +

      获取当前系统时间(resource-id: mobile-resource)

      + +

      获取当前系统时间, 需要access_token的 resource-id 为 mobile-resource 才能访问

      + +
        +
      • +

        + 请求URI: /mobile/system_time GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        + 请求示例: +
        http://localhost:8083/mobile/system_time?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOT...
        + +
        curl --location 'http://localhost:8083/mobile/system_time' \
        +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiNDllOTQ0N2E2ZGJlMTUyZTBkMmE2YjYzNDRmZmQyNWQiLCJpYXQiOjE2OTU4NjY1NDAsImV4cCI6MTY5NTkwOTc0MCwiYXVkIjoibW9iaWxlLWNsaWVudCJ9.WjB1lvsccXXBJiOeHMuvo-kZmpfpi1YQgU8NuGYmR70'
        +
        + +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          {
          +    "time": 1695628213913
          +}
          +
          +
        • +
        • +
          + 异常 [401]
          +
          {"error":"invalid_token","error_description":"Invalid access_token: 95c3afd44c5d87301dc3034b20b3fc75s"}
          +
          +
        • +
        +
      • +
      +
      + +
      +

      [resources]

      + +

      获取当前用户信息 (resource-id: os-resource; Role: User)

      + +

      使用access_token获取用户信息, 需要access_token的 resource-id 为 os-resource 且用户Role包含 User + 才能访问

      + +
        +
      • +

        + 请求URI: /rs/username GET +

        + +
        + 请求参数说明: + + + + + + + + + + + + + + +
        参数名参数值必须?备注
        + 请求示例: +
        http://localhost:8083/rs/username?access_token=eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIi...
        + +
        curl --location 'http://localhost:8083/rs/username' \
        +--header 'Authorization: Bearer eyJraWQiOiJvYXV0aDItc2hpcm8ta2V5aWQiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0ZXN0IiwianRpIjoiZTUyYTExZGU4Y2EwNGUwOTI1Y2RhNDMxNDYwN2NmZGUiLCJpYXQiOjE2OTU4NjY2NzksImV4cCI6MTY5NTkwOTg3OSwiYXVkIjoidGVzdC1jbGllbnQifQ.qLkvnAartpxkiFfeMwnzrK61ihJtXAu6ml5tFU8O-NU'
        +
        + +
        +
        + + 响应 + +
          +
        • +
          + 正常 [200]
          +
          {
          +    "clientId": "test-client",
          +    "username": "test"
          +}
          +
          +
        • +
        • +
          + 异常 [401]
          +
          {"error":"invalid_token","error_description":"Invalid client by token: 95c3afd44c5d87301dc3034b20b3fc75"}
          +
          +
        • +
        +
      • +
      +
      + +
      +
      + + +
      +
      +
      +
      + © 2015-2023 oauth2-shiro +
      +
      +
      + + \ No newline at end of file diff --git a/resources/src/main/resources/templates/fragments/main.html b/resources/src/main/resources/templates/fragments/main.html new file mode 100644 index 0000000..183ad85 --- /dev/null +++ b/resources/src/main/resources/templates/fragments/main.html @@ -0,0 +1,32 @@ + + + + + + Fragments + +
      + + +
      + + + + + + + + \ No newline at end of file diff --git a/resources/src/main/resources/templates/welcome.html b/resources/src/main/resources/templates/welcome.html new file mode 100644 index 0000000..4778fa3 --- /dev/null +++ b/resources/src/main/resources/templates/welcome.html @@ -0,0 +1,60 @@ + + + + + + + + + OAuth2-Shiro[resources] + + + + +
      + + + +
      +

      + [resources]模块用于提供资源(resource), 是 access_token 的消费者, 在实际应用中, 属于业务系统(认证,安全交给 authz 模块处理), 对其API的调用需要access_token; + 需要先从authz模块获取access_token. +

      + + 操作说明 + +
        +
      1. +

        resources 模块提供了两个API接口,需要不同权限的access_token才可以访问, API分别为:

        + +

        + a. /mobile/system_time -- 获取系统时间 +

        + +

        + b. /rs/username -- 获取用户信息 +

        + +

        + 对API的详细使用请查看文档 OS_API +

        +
      2. +
      3. +

        对于每一类资源(resource), 需要定义一个 resource-id, 并且需要在 RsSecurityConfig.java 中配置一个OAuth2Filter, 然后在 + shiroFilter 中添加该 Filter与配置URL Pattern; 详细请参数项目中 RsSecurityConfig.java 的配置

        +
      4. +
      5. +

        + 注意 [resources]模块的配置文件 application.properties(位于src/main/resources目录) + 中数据库连接配置信息必须与 [authz]模块中的配置一致. 因为[authz]模块获取token后存入数据库, [resources]模块在调用时从数据库中查询获取. +

        +
      6. +
      +
      + +
      +
      + + \ No newline at end of file -- Gitee