diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..a9ae158608 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# 4 space - Tab indentation +[*.{java,xml,js,html}] +indent_style = tab +indent_size = 4 diff --git a/.gitignore b/.gitignore index 016a3b8f82..e663c16b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -local-values.conf target *~ bin @@ -10,3 +9,4 @@ bin .classpath /target .springBeans +nb-configuration.xml diff --git a/.travis.yml b/.travis.yml index dff5f3a5d0..3d619c8263 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,11 @@ language: java +jdk: + - oraclejdk11 +sudo: false + +after_success: + - bash <(curl -s https://codecov.io/bash) + +cache: + directories: + - $HOME/.m2 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..96c6356a0c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +Unreleased: + +*1.3.3*: +- Authorization codes are now longer +- Client/RS can parse the "sub" and "user_id" claims in introspection response +- Database-direct queries for fetching tokens by user (optimization) +- Device flow supports verification_uri_complete (must be turned on) +- Long scopes display properly and are still checkable +- Language system remebers when it can't find a file and stops throwing so many errors +- Index added for refresh tokens +- Updated to Spring Security 4.2.11 +- Updated Spring to 4.3.22 +- Change approve pages to use issuer instead of page context +- Updated oracle database scripts + +*1.3.2*: +- Added changelog +- Set default redirect URI resolver strict matching to true +- Fixed XSS vulnerability on redirect URI display on approval page +- Removed MITRE from copyright +- Disallow unsigned JWTs on client authentication +- Upgraded Nimbus revision +- Added French translation +- Added hooks for custom JWT claims +- Removed "Not Yet Implemented" tag from post-logout redirect URI + +*1.3.1*: +- Added End Session endpoint +- Fixed discovery endpoint +- Downgrade MySQL connector dependency version from developer preview to GA release + +*1.3.0*: +- Added device flow support +- Added PKCE support +- Modularized UI to allow better overlay and extensions +- Modularized data import/export API +- Added software statements to dynamic client registration +- Added assertion processing framework +- Removed ID tokens from storage +- Removed structured scopes + +*1.2.6*: +- Added strict HEART compliance mode diff --git a/LICENSE.txt b/LICENSE.txt index 1a04bce423..0e640e493b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,9 @@ -Copyright 2014 The MITRE Corporation - and the MIT Kerberos and Internet Trust Consortium +Copyright 2018 The MIT Internet Trust Consortium + +Portions copyright 2011-2013 The MITRE Corporation Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. +you may not use this project except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000000..610579f550 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# MITREid Connect +--- + +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent) [![Travis CI](https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server.svg?branch=master)](https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server) [![Codecov](https://codecov.io/github/mitreid-connect/OpenID-Connect-Java-Spring-Server/coverage.svg?branch=master)](https://codecov.io/github/mitreid-connect/OpenID-Connect-Java-Spring-Server) + +This project contains a certified OpenID Connect reference implementation in Java on the Spring platform, including a functioning [server library](openid-connect-server), [deployable server package](openid-connect-server-webapp), [client (RP) library](openid-connect-client), and general [utility libraries](openid-connect-common). The server can be used as an OpenID Connect Identity Provider as well as a general-purpose OAuth 2.0 Authorization Server. + +[![OpenID Certified](https://cloud.githubusercontent.com/assets/1454075/7611268/4d19de32-f97b-11e4-895b-31b2455a7ca6.png)](https://openid.net/certification/) + +More information about the project can be found: + +* [The project homepage on GitHub (with related projects)](https://github.com/mitreid-connect/) +* [Full documentation](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki) +* [Documentation for the Maven project and Java API](http://mitreid-connect.github.com/) +* [Issue tracker (for bug reports and support requests)](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues) +* The mailing list for the project can be found at `mitreid-connect@mit.edu`, with [archives available online](https://mailman.mit.edu/mailman/listinfo/mitreid-connect). + + +The authors and key contributors of the project include: + +* [Justin Richer](https://github.com/jricher/) +* [Amanda Anganes](https://github.com/aanganes/) +* [Michael Jett](https://github.com/jumbojett/) +* [Michael Walsh](https://github.com/nemonik/) +* [Steve Moore](https://github.com/srmoore) +* [Mike Derryberry](https://github.com/mtderryberry) +* [William Kim](https://github.com/wikkim) +* [Mark Janssen](https://github.com/praseodym) + + +Licensed under the Apache 2.0 license, for details see `LICENSE.txt`. diff --git a/README.txt b/README.txt deleted file mode 100644 index 41b58d9af5..0000000000 --- a/README.txt +++ /dev/null @@ -1,33 +0,0 @@ -An OpenID Connect reference implementation in Java on the Spring platform. For license information see LICENSE.txt. - -This code includes a functioning server (IdP) and client (RP) as well as utility libraries. - -The project homepage on GitHub is: - - https://github.com/mitreid-connect/ - -Full documentation can be found online: - - https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki - -Documentation for the Maven project and Java API can be found at: - - http://mitreid-connect.github.com/ - -Issues can be reported at: - - https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues - -The mailing list for the project can be found at mitreid-connect@mit.edu, with archives available online: - - https://mailman.mit.edu/mailman/listinfo/mitreid-connect - - - -Authors: Justin Richer, Amanda Anganes, Michael Walsh, Michael Jett, Steve Moore, Mike Derryberry, William Kim - - - - -Copyright 2014, The MITRE Corporation (http://www.mitre.org/) - and the MIT Kerberos and Internet Trust Consortium (http://kit.mit.edu/) diff --git a/README_zh_CN.md b/README_zh_CN.md new file mode 100644 index 0000000000..4933b36836 --- /dev/null +++ b/README_zh_CN.md @@ -0,0 +1,38 @@ +# MITREid Connect +--- + +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.mitre/openid-connect-parent) [![Travis CI](https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server.svg?branch=master)](https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server) + +此项目提供了一个业经认证的、用Java语言构筑于Spring平台之上的OpenID Connect参考实现,包括 [服务器端的实现库](openid-connect-server), [可部署的服务器包](openid-connect-server-webapp), [客户端 (RP) 的库](openid-connect-client), 以及 [工具类库](openid-connect-common)。该服务器可以用做OpenID Connect身份提供者,也可以用做一般意义上的OAuth 2.0授权服务器。 + +[![OpenID认证](https://cloud.githubusercontent.com/assets/1454075/7611268/4d19de32-f97b-11e4-895b-31b2455a7ca6.png)](https://openid.net/certification/) + +有关项目的更多信息参见: + +* [项目在GitHub上的主页 (及相关项目)](https://github.com/mitreid-connect/) +* [完整的文档](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki) +* [Maven文档及Java API](http://mitreid-connect.github.com/) +* [问题(Issue)追踪系统 (用于报告bug及提交支持请求)](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues) +* 项目的邮件列表: `mitreid-connect@mit.edu`, 及其 [在线存档](https://mailman.mit.edu/mailman/listinfo/mitreid-connect). + + +项目的作者及主要贡献者有: + +* [Justin Richer](https://github.com/jricher/) +* [Amanda Anganes](https://github.com/aanganes/) +* [Michael Jett](https://github.com/jumbojett/) +* [Michael Walsh](https://github.com/nemonik/) +* [Steve Moore](https://github.com/srmoore) +* [Mike Derryberry](https://github.com/mtderryberry) +* [William Kim](https://github.com/wikkim) +* [Mark Janssen](https://github.com/praseodym) + + +项目的中文译者: + +* [刘晓曦](https://github.com/liouxiao/) + + + + +版权所有 ©2018 [MIT因特网信任联盟](http://www.mit-trust.org/). 采用Apache 2.0许可证, 详见 `LICENSE.txt`. diff --git a/checkstyle.xml b/checkstyle.xml index 7b96bdff67..06129daddb 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1,20 +1,21 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> diff --git a/docs/OAuth2.0_Diagrams.pdf b/docs/OAuth2.0_Diagrams.pdf deleted file mode 100755 index 55b525e92b..0000000000 Binary files a/docs/OAuth2.0_Diagrams.pdf and /dev/null differ diff --git a/docs/OpenID_Connect_Diagrams.pdf b/docs/OpenID_Connect_Diagrams.pdf deleted file mode 100644 index a8da67ec49..0000000000 Binary files a/docs/OpenID_Connect_Diagrams.pdf and /dev/null differ diff --git a/openid-connect-client/README.md b/openid-connect-client/README.md index 13fbb7337f..5bddcdb6ed 100644 --- a/openid-connect-client/README.md +++ b/openid-connect-client/README.md @@ -2,132 +2,11 @@ ## Overview ## -This project contains an OpenID Connect Client implemented as a Spring Security AuthenticationFilter. The client facilitates a user's authentication into the secured application to an OpenID Connect Java Spring Server following the OpenID Connect Standard protocol. - -For an example of the Client configuration, see the [Simple Web App] project. +This project contains an OpenID Connect Client implemented as a Spring Security AuthenticationFilter. The client facilitates a user's authentication into the secured application to an OpenID Connect Server following the OpenID Connect standard protocol. ## Configuring ## -Configure the client by adding the following XML to your application context security making changes where necessary for your specific deployment. - -Open and define an HTTP security configuration with a reference to a custom ***AuthenticationEntryPoint***, described below: - - - -Specify the access attributes and/or filter list for a particular set of URLs needing protection: - - - -Indicate that ***OIDCAuthenticationFilter*** authentication filter should be incorporated into the security filter chain: - - - -Then close the HTTP security configuration: - - - -Define a custom ***AuthenticationEntryPoint*** to use a login URL via a bean declaration: - - - - - -NOTE: The ***loginFormUrl*** value is post-pended to the URI of the application being secured to define the ***redirect_uri***, the value passed to the OIDC Server and, if the ***OIDCAuthenticationUsingChooserFilter*** is configured, also the Account Chooser Application. - -Define an ***AuthenticationManager*** with a reference to a custom authentication provider, ***OpenIDConnectAuthenticationProvider***: - - - - - -Define the custom authentication provider. Note that it does not take a UserDetailsService as input at this time but instead makes a call to the UserInfoEndpoint to fill in user information. - - - -### Configuring the OIDCAuthenticationFilter ### - -The ***OIDCAuthenticationFilter*** filter is defined with the following properties: - -* ***authenticationManager*** -- a reference to the ***AuthenticationManager*** -* ***errorRedirectURI*** -- the URI of the Error redirect - -Additionally, it contains a set of convenience methods to pass through to parameters on the ***OIDCServerConfiguration*** object that defines attributes of the server that it connects to: - -* ***issuer*** -- the root issuer string of this server (required) -* ***authorizationEndpointUrl*** -- the URL of the Authorization Endpoint (required) -* ***tokenEndpointUrl*** -- the URL of the Token Endpoint (required) -* ***jwkSigningUrl*** -- the URL of the JWK (public key) Endpoint for token verification -* ***clientId*** -- the registered client identifier (required) -* ***clientSecret*** -- the registered client secret -* ***userInfoUrl*** -- the URL of the User Info Endpoint -* ***scope*** -- space-separated list of scopes; the required value "openid" will always be prepended to the list given here - -Configure like so: - - - - - - - - - - - - - - -### Configuring the OIDCAuthenticationUsingChooserFilter ### - -For talking to multiple IdPs using an Account chooser, the ***OIDCAuthenticationUsingChooserFilter*** can be configured and used. [The Client -- Account Chooser protocol] documentation details the protocol used between the Client and an Account Chooser application. - -The ***OIDCAuthenticationUsingChooserFilter*** Authentication Filter has the following properties: - -* ***authenticationManager*** -- a reference to the ***AuthenticationManager***, -* ***errorRedirectURI*** -- the URI of the Error redirect, -* ***accountChooserURI*** -- to denote the URI of the Account Chooser, and -* ***accountChooserClient*** -- to identify the Client to the Account Chooser UI application. -* ***oidcServerConfigs*** -- a map of ***OIDCserverConfiguration***s to encapsulate the settings necesary for the client to communicate with each respective OIDC server, - -Each ***OIDCServerConfiguration*** entry in ***OIDCserverConfiguration*** map is keyed to the ***issuer*** returned from the Account Chooser Application and enumerates the following properties: +For an example of the Client configuration, see the [Simple Web App](https://github.com/mitreid-connect/simple-web-app) project. -* ***authenticationManager*** -- a reference to the ***AuthenticationManager***, -* ***issuer*** -- the root issuer string of this server (required) -* ***authorizationEndpointUrl*** -- the URL of the Authorization Endpoint (required) -* ***tokenEndpointUrl*** -- the URL of the Token Endpoint (required) -* ***jwkSigningUrl*** -- the URL of the JWK (public key) Endpoint for token verification -* ***clientId*** -- the registered client identifier (required) -* ***clientSecret*** -- the registered client secret -* ***userInfoUrl*** -- the URL of the User Info Endpoint -* ***scope*** -- space-separated list of scopes; the required value "openid" will always be prepended to the list given here +Full documentation is available on the [project documentation wiki pages](https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/wiki/Client-configuration). -Configure like so: - - - - - - - - - - - - - - - - - - - - - + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> - 4.0.0 - - openid-connect-parent - org.mitre - 1.1.2-SNAPSHOT - .. - - openid-connect-client - OpenID Connect Client filter for Spring Security - OpenID Connect Client - - - org.mitre - openid-connect-common - 1.1.2-SNAPSHOT - - - jar - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${java-version} - ${java-version} - - - - - org.apache.maven.plugins - maven-source-plugin - 2.1.2 - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.7 - - - attach-sources - - jar - - - - - - + 4.0.0 + + openid-connect-parent + org.mitre + 1.3.5-SNAPSHOT + .. + + openid-connect-client + OpenID Connect Client filter for Spring Security + OpenID Connect Client + + + org.mitre + openid-connect-common + + + jar + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java-version} + ${java-version} + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-sources + + jar + + + + + + diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java index 6196b2d46d..b311a84d9c 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/IntrospectingTokenService.java @@ -1,25 +1,27 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.introspectingfilter; import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC; import java.io.IOException; import java.net.URI; +import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -27,7 +29,7 @@ import java.util.Set; import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter; import org.mitre.oauth2.introspectingfilter.service.IntrospectionConfigurationService; import org.mitre.oauth2.introspectingfilter.service.impl.SimpleIntrospectionAuthorityGranter; @@ -67,22 +69,49 @@ public class IntrospectingTokenService implements ResourceServerTokenServices { private IntrospectionConfigurationService introspectionConfigurationService; private IntrospectionAuthorityGranter introspectionAuthorityGranter = new SimpleIntrospectionAuthorityGranter(); - private HttpClient httpClient = new SystemDefaultHttpClient(); - private HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + private int defaultExpireTime = 300000; // 5 minutes in milliseconds + private boolean forceCacheExpireTime = false; // force removal of cached tokens based on default expire time + private boolean cacheNonExpiringTokens = false; + private boolean cacheTokens = true; + + private HttpComponentsClientHttpRequestFactory factory; + + public IntrospectingTokenService() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public IntrospectingTokenService(HttpClient httpClient) { + this.factory = new HttpComponentsClientHttpRequestFactory(httpClient); + } // Inner class to store in the hash map private class TokenCacheObject { OAuth2AccessToken token; OAuth2Authentication auth; + Date cacheExpire; private TokenCacheObject(OAuth2AccessToken token, OAuth2Authentication auth) { this.token = token; this.auth = auth; + + // we don't need to check the cacheTokens values, because this won't actually be added to the cache if cacheTokens is false + // if the token isn't null we use the token expire time + // if forceCacheExpireTime is also true, we also make sure that the token expire time is shorter than the default expire time + if ((this.token.getExpiration() != null) && (!forceCacheExpireTime || (forceCacheExpireTime && (this.token.getExpiration().getTime() - System.currentTimeMillis() <= defaultExpireTime)))) { + this.cacheExpire = this.token.getExpiration(); + } else { // if the token doesn't have an expire time, or if the using forceCacheExpireTime the token expire time is longer than the default, then use the default expire time + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.MILLISECOND, defaultExpireTime); + this.cacheExpire = cal.getTime(); + } } } - private Map authCache = new HashMap(); - private static Logger logger = LoggerFactory.getLogger(IntrospectingTokenService.class); + private Map authCache = new HashMap<>(); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(IntrospectingTokenService.class); /** * @return the introspectionConfigurationService @@ -98,12 +127,98 @@ public void setIntrospectionConfigurationService(IntrospectionConfigurationServi this.introspectionConfigurationService = introspectionUrlProvider; } - // Check if there is a token and authentication in the cache - // and check if it is not expired. + /** + * @param introspectionAuthorityGranter the introspectionAuthorityGranter to set + */ + public void setIntrospectionAuthorityGranter(IntrospectionAuthorityGranter introspectionAuthorityGranter) { + this.introspectionAuthorityGranter = introspectionAuthorityGranter; + } + + /** + * @return the introspectionAuthorityGranter + */ + public IntrospectionAuthorityGranter getIntrospectionAuthorityGranter() { + return introspectionAuthorityGranter; + } + + /** + * get the default cache expire time in milliseconds + * @return + */ + public int getDefaultExpireTime() { + return defaultExpireTime; + } + + /** + * set the default cache expire time in milliseconds + * @param defaultExpireTime + */ + public void setDefaultExpireTime(int defaultExpireTime) { + this.defaultExpireTime = defaultExpireTime; + } + + /** + * check if forcing a cache expire time maximum value + * @return the forceCacheExpireTime setting + */ + public boolean isForceCacheExpireTime() { + return forceCacheExpireTime; + } + + /** + * set forcing a cache expire time maximum value + * @param forceCacheExpireTime + */ + public void setForceCacheExpireTime(boolean forceCacheExpireTime) { + this.forceCacheExpireTime = forceCacheExpireTime; + } + + /** + * Are non-expiring tokens cached using the default cache time + * @return state of cacheNonExpiringTokens + */ + public boolean isCacheNonExpiringTokens() { + return cacheNonExpiringTokens; + } + + /** + * should non-expiring tokens be cached using the default cache timeout + * @param cacheNonExpiringTokens + */ + public void setCacheNonExpiringTokens(boolean cacheNonExpiringTokens) { + this.cacheNonExpiringTokens = cacheNonExpiringTokens; + } + + /** + * Is the service caching tokens, or is it hitting the introspection end point every time + * @return true is caching tokens locally, false hits the introspection end point every time + */ + public boolean isCacheTokens() { + return cacheTokens; + } + + /** + * Configure if the client should cache tokens locally or not + * @param cacheTokens + */ + public void setCacheTokens(boolean cacheTokens) { + this.cacheTokens = cacheTokens; + } + + /** + * Check to see if the introspection end point response for a token has been cached locally + * This call will return the token if it has been cached and is still valid according to + * the cache expire time on the TokenCacheObject. If a cached value has been found but is + * expired, either by default expire times or the token's own expire time, then the token is + * removed from the cache and null is returned. + * @param key is the token to check + * @return the cached TokenCacheObject or null + */ private TokenCacheObject checkCache(String key) { - if (authCache.containsKey(key)) { + if (cacheTokens && authCache.containsKey(key)) { TokenCacheObject tco = authCache.get(key); - if (tco.token.getExpiration().after(new Date())) { + + if (tco != null && tco.cacheExpire != null && tco.cacheExpire.after(new Date())) { return tco; } else { // if the token is expired, don't keep things around. @@ -115,19 +230,27 @@ private TokenCacheObject checkCache(String key) { private OAuth2Request createStoredRequest(final JsonObject token) { String clientId = token.get("client_id").getAsString(); - Set scopes = new HashSet(); - for (JsonElement e : token.get("scope").getAsJsonArray()) { - scopes.add(e.getAsString()); + Set scopes = new HashSet<>(); + if (token.has("scope")) { + scopes.addAll(OAuth2Utils.parseParameterList(token.get("scope").getAsString())); } - Map parameters = new HashMap(); + Map parameters = new HashMap<>(); parameters.put("client_id", clientId); parameters.put("scope", OAuth2Utils.formatParameterList(scopes)); OAuth2Request storedRequest = new OAuth2Request(parameters, clientId, null, true, scopes, null, null, null, null); return storedRequest; } - private Authentication createAuthentication(JsonObject token) { - return new PreAuthenticatedAuthenticationToken(token.get("sub").getAsString(), token, introspectionAuthorityGranter.getAuthorities(token)); + private Authentication createUserAuthentication(JsonObject token) { + JsonElement userId = token.get("user_id"); + if(userId == null) { + userId = token.get("sub"); + if (userId == null) { + return null; + } + } + + return new PreAuthenticatedAuthenticationToken(userId.getAsString(), token, introspectionAuthorityGranter.getAuthorities(token)); } private OAuth2AccessToken createAccessToken(final JsonObject token, final String tokenString) { @@ -135,10 +258,14 @@ private OAuth2AccessToken createAccessToken(final JsonObject token, final String return accessToken; } - // Validate a token string against the introspection endpoint, - // then parse it and store it in the local cache. Return true on - // sucess, false otherwise. - private boolean parseToken(String accessToken) { + /** + * Validate a token string against the introspection endpoint, + * then parse it and store it in the local cache if caching is enabled. + * + * @param accessToken Token to pass to the introspection endpoint + * @return TokenCacheObject containing authentication and token if the token was valid, otherwise null + */ + private TokenCacheObject parseToken(String accessToken) { // find out which URL to ask String introspectionUrl; @@ -148,14 +275,14 @@ private boolean parseToken(String accessToken) { client = introspectionConfigurationService.getClientConfiguration(accessToken); } catch (IllegalArgumentException e) { logger.error("Unable to load introspection URL or client configuration", e); - return false; + return null; } // Use the SpringFramework RestTemplate to send the request to the // endpoint String validatedToken = null; RestTemplate restTemplate; - MultiValueMap form = new LinkedMultiValueMap(); + MultiValueMap form = new LinkedMultiValueMap<>(); final String clientId = client.getClientId(); final String clientSecret = client.getClientSecret(); @@ -185,12 +312,13 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE validatedToken = restTemplate.postForObject(introspectionUrl, form, String.class); } catch (RestClientException rce) { logger.error("validateToken", rce); + return null; } if (validatedToken != null) { // parse the json JsonElement jsonRoot = new JsonParser().parse(validatedToken); if (!jsonRoot.isJsonObject()) { - return false; // didn't get a proper JSON object + return null; // didn't get a proper JSON object } JsonObject tokenResponse = jsonRoot.getAsJsonObject(); @@ -198,29 +326,31 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE if (tokenResponse.get("error") != null) { // report an error? logger.error("Got an error back: " + tokenResponse.get("error") + ", " + tokenResponse.get("error_description")); - return false; + return null; } if (!tokenResponse.get("active").getAsBoolean()) { // non-valid token logger.info("Server returned non-active token"); - return false; + return null; } // create an OAuth2Authentication - OAuth2Authentication auth = new OAuth2Authentication(createStoredRequest(tokenResponse), createAuthentication(tokenResponse)); + OAuth2Authentication auth = new OAuth2Authentication(createStoredRequest(tokenResponse), createUserAuthentication(tokenResponse)); // create an OAuth2AccessToken OAuth2AccessToken token = createAccessToken(tokenResponse, accessToken); - if (token.getExpiration().after(new Date())) { + if (token.getExpiration() == null || token.getExpiration().after(new Date())) { // Store them in the cache - authCache.put(accessToken, new TokenCacheObject(token, auth)); - - return true; + TokenCacheObject tco = new TokenCacheObject(token, auth); + if (cacheTokens && (cacheNonExpiringTokens || token.getExpiration() != null)) { + authCache.put(accessToken, tco); + } + return tco; } } - // If we never put a token and an authentication in the cache... - return false; + // when the token is invalid for whatever reason + return null; } @Override @@ -232,13 +362,9 @@ public OAuth2Authentication loadAuthentication(String accessToken) throws Authen if (cacheAuth != null) { return cacheAuth.auth; } else { - if (parseToken(accessToken)) { - cacheAuth = authCache.get(accessToken); - if (cacheAuth != null && (cacheAuth.token.getExpiration().after(new Date()))) { - return cacheAuth.auth; - } else { - return null; - } + cacheAuth = parseToken(accessToken); + if (cacheAuth != null) { + return cacheAuth.auth; } else { return null; } @@ -254,13 +380,9 @@ public OAuth2AccessToken readAccessToken(String accessToken) { if (cacheAuth != null) { return cacheAuth.token; } else { - if (parseToken(accessToken)) { - cacheAuth = authCache.get(accessToken); - if (cacheAuth != null && (cacheAuth.token.getExpiration().after(new Date()))) { - return cacheAuth.token; - } else { - return null; - } + cacheAuth = parseToken(accessToken); + if (cacheAuth != null) { + return cacheAuth.token; } else { return null; } diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/OAuth2AccessTokenImpl.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/OAuth2AccessTokenImpl.java index 166747953a..723fcc54d0 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/OAuth2AccessTokenImpl.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/OAuth2AccessTokenImpl.java @@ -1,31 +1,27 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.introspectingfilter; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; @@ -37,26 +33,21 @@ public class OAuth2AccessTokenImpl implements OAuth2AccessToken { - private JsonObject token; + private JsonObject introspectionResponse; private String tokenString; - private Set scopes = new HashSet(); + private Set scopes = new HashSet<>(); private Date expireDate; - public OAuth2AccessTokenImpl(JsonObject token, String tokenString) { - this.token = token; + public OAuth2AccessTokenImpl(JsonObject introspectionResponse, String tokenString) { + this.setIntrospectionResponse(introspectionResponse); this.tokenString = tokenString; - if (token.get("scope") != null) { - scopes = Sets.newHashSet(Splitter.on(" ").split(token.get("scope").getAsString())); + if (introspectionResponse.get("scope") != null) { + scopes = Sets.newHashSet(Splitter.on(" ").split(introspectionResponse.get("scope").getAsString())); } - DateFormat dateFormater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - if (token.get("exp") != null) { - try { - expireDate = dateFormater.parse(token.get("exp").getAsString()); - } catch (ParseException ex) { - Logger.getLogger(IntrospectingTokenService.class.getName()).log(Level.SEVERE, null, ex); - } + if (introspectionResponse.get("exp") != null) { + expireDate = new Date(introspectionResponse.get("exp").getAsLong() * 1000L); } } @@ -107,4 +98,20 @@ public String getValue() { return tokenString; } + + /** + * @return the token + */ + public JsonObject getIntrospectionResponse() { + return introspectionResponse; + } + + + /** + * @param token the token to set + */ + public void setIntrospectionResponse(JsonObject token) { + this.introspectionResponse = token; + } + } diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java index 4317c7cceb..d514bfbabe 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionAuthorityGranter.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.introspectingfilter.service; diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java index 6b73029e43..fe85727b5b 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/IntrospectionConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.introspectingfilter.service; diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java index 04b03db653..b60179f604 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/JWTParsingIntrospectionConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.introspectingfilter.service.impl; @@ -32,11 +33,11 @@ import com.nimbusds.jwt.JWTParser; /** - * + * * Parses the incoming accesstoken as a JWT and determines the issuer based on * the "iss" field inside the JWT. Uses the ServerConfigurationService to determine * the introspection URL for that issuer. - * + * * @author jricher * */ @@ -59,6 +60,12 @@ public void setServerConfigurationService(ServerConfigurationService serverConfi this.serverConfigurationService = serverConfigurationService; } + /** + * @param clientConfigurationService the clientConfigurationService to set + */ + public void setClientConfigurationService(ClientConfigurationService clientConfigurationService) { + this.clientConfigurationService = clientConfigurationService; + } private String getIssuer(String accessToken) { try { diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/ScopeBasedIntrospectionAuthoritiesGranter.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/ScopeBasedIntrospectionAuthoritiesGranter.java new file mode 100644 index 0000000000..26bc7f11c7 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/ScopeBasedIntrospectionAuthoritiesGranter.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.introspectingfilter.service.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.mitre.oauth2.introspectingfilter.service.IntrospectionAuthorityGranter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.util.OAuth2Utils; + +import com.google.gson.JsonObject; + +/** + * @author jricher + * + */ +public class ScopeBasedIntrospectionAuthoritiesGranter implements IntrospectionAuthorityGranter { + + private List authorities = AuthorityUtils.createAuthorityList("ROLE_API"); + + /* (non-Javadoc) + * @see org.mitre.oauth2.introspectingfilter.IntrospectionAuthorityGranter#getAuthorities(net.minidev.json.JSONObject) + */ + @Override + public List getAuthorities(JsonObject introspectionResponse) { + List auth = new ArrayList<>(getAuthorities()); + + if (introspectionResponse.has("scope") && introspectionResponse.get("scope").isJsonPrimitive()) { + String scopeString = introspectionResponse.get("scope").getAsString(); + Set scopes = OAuth2Utils.parseParameterList(scopeString); + for (String scope : scopes) { + auth.add(new SimpleGrantedAuthority("OAUTH_SCOPE_" + scope)); + } + } + + return auth; + } + + /** + * @return the authorities + */ + public List getAuthorities() { + return authorities; + } + + /** + * @param authorities the authorities to set + */ + public void setAuthorities(List authorities) { + this.authorities = authorities; + } + +} diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java index 2b38c50d57..45126f2463 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/SimpleIntrospectionAuthorityGranter.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.introspectingfilter.service.impl; @@ -28,9 +29,9 @@ import com.google.gson.JsonObject; /** - * + * * Grants the same set of authorities no matter what's passed in. - * + * * @author jricher * */ diff --git a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java index dfe8d2eb98..5aa370c415 100644 --- a/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/oauth2/introspectingfilter/service/impl/StaticIntrospectionConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.introspectingfilter.service.impl; @@ -23,10 +24,10 @@ import org.mitre.oauth2.model.RegisteredClient; /** - * + * * Always provides the (configured) IntrospectionURL and RegisteredClient regardless * of token. Useful for talking to a single, trusted authorization server. - * + * * @author jricher * */ diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AuthorizationEndpointException.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AuthorizationEndpointException.java new file mode 100644 index 0000000000..0fe0c7e714 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/AuthorizationEndpointException.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.openid.connect.client; + +import org.springframework.security.authentication.AuthenticationServiceException; + +public class AuthorizationEndpointException extends AuthenticationServiceException { + + private static final long serialVersionUID = 6953119789654778380L; + + private String error; + + private String errorDescription; + + private String errorURI; + + public AuthorizationEndpointException(String error, String errorDescription, String errorURI) { + super("Error from Authorization Endpoint: " + error + " " + errorDescription + " " + errorURI); + this.error = error; + this.errorDescription = errorDescription; + this.errorURI = errorURI; + } + + public String getError() { + return error; + } + + public String getErrorDescription() { + return errorDescription; + } + + public String getErrorURI() { + return errorURI; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "AuthorizationEndpointException [error=" + error + ", errorDescription=" + errorDescription + ", errorURI=" + errorURI + "]"; + } +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/NamedAdminAuthoritiesMapper.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/NamedAdminAuthoritiesMapper.java index 0565332fa1..1d1e810f6a 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/NamedAdminAuthoritiesMapper.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/NamedAdminAuthoritiesMapper.java @@ -1,67 +1,78 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client; +import java.text.ParseException; import java.util.Collection; import java.util.HashSet; import java.util.Set; +import org.mitre.openid.connect.model.UserInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; /** - * + * * Simple mapper that adds ROLE_USER to the authorities map for all queries, * plus adds ROLE_ADMIN if the subject and issuer pair are found in the * configurable "admins" set. - * + * * @author jricher - * + * */ -public class NamedAdminAuthoritiesMapper implements GrantedAuthoritiesMapper { +public class NamedAdminAuthoritiesMapper implements OIDCAuthoritiesMapper { + + private static Logger logger = LoggerFactory.getLogger(NamedAdminAuthoritiesMapper.class); private static final SimpleGrantedAuthority ROLE_ADMIN = new SimpleGrantedAuthority("ROLE_ADMIN"); private static final SimpleGrantedAuthority ROLE_USER = new SimpleGrantedAuthority("ROLE_USER"); - private Set admins = new HashSet(); - - private GrantedAuthoritiesMapper chain = new NullAuthoritiesMapper(); + private Set admins = new HashSet<>(); @Override - public Collection mapAuthorities(Collection authorities) { + public Collection mapAuthorities(JWT idToken, UserInfo userInfo) { + + Set out = new HashSet<>(); + try { + JWTClaimsSet claims = idToken.getJWTClaimsSet(); - Set out = new HashSet(); - out.addAll(authorities); + SubjectIssuerGrantedAuthority authority = new SubjectIssuerGrantedAuthority(claims.getSubject(), claims.getIssuer()); + out.add(authority); - for (GrantedAuthority authority : authorities) { if (admins.contains(authority)) { out.add(ROLE_ADMIN); } - } - // everybody's a user by default - out.add(ROLE_USER); + // everybody's a user by default + out.add(ROLE_USER); - return chain.mapAuthorities(out); + } catch (ParseException e) { + logger.error("Unable to parse ID Token inside of authorities mapper (huh?)"); + } + return out; } /** @@ -78,18 +89,4 @@ public void setAdmins(Set admins) { this.admins = admins; } - /** - * @return the chain - */ - public GrantedAuthoritiesMapper getChain() { - return chain; - } - - /** - * @param chain the chain to set - */ - public void setChain(GrantedAuthoritiesMapper chain) { - this.chain = chain; - } - } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java index 20c54b2358..8412525471 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java @@ -1,41 +1,50 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.PRIVATE_KEY; import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC; +import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_JWT; import java.io.IOException; import java.math.BigInteger; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.text.ParseException; import java.util.Date; import java.util.Map; +import java.util.UUID; -import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; -import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.HttpClientBuilder; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.mitre.jwt.signer.service.impl.JWKSetCacheService; +import org.mitre.jwt.signer.service.impl.SymmetricKeyJWTValidatorCacheService; +import org.mitre.oauth2.model.PKCEAlgorithm; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.client.model.IssuerServiceResponse; import org.mitre.openid.connect.client.service.AuthRequestOptionsService; @@ -45,7 +54,7 @@ import org.mitre.openid.connect.client.service.ServerConfigurationService; import org.mitre.openid.connect.client.service.impl.StaticAuthRequestOptionsService; import org.mitre.openid.connect.config.ServerConfiguration; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; @@ -57,56 +66,87 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; -import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriUtils; import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.nimbusds.jose.Algorithm; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.util.Base64; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; +import com.nimbusds.jwt.PlainJWT; import com.nimbusds.jwt.SignedJWT; /** * OpenID Connect Authentication Filter class - * + * * @author nemonik, jricher - * + * */ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFilter { protected final static String REDIRECT_URI_SESION_VARIABLE = "redirect_uri"; + protected final static String CODE_VERIFIER_SESSION_VARIABLE = "code_verifier"; protected final static String STATE_SESSION_VARIABLE = "state"; protected final static String NONCE_SESSION_VARIABLE = "nonce"; protected final static String ISSUER_SESSION_VARIABLE = "issuer"; - protected static final String TARGET_SESSION_VARIABLE = "target"; + protected final static String TARGET_SESSION_VARIABLE = "target"; protected final static int HTTP_SOCKET_TIMEOUT = 30000; - protected final static String FILTER_PROCESSES_URL = "/openid_connect_login"; + public final static String FILTER_PROCESSES_URL = "/openid_connect_login"; // Allow for time sync issues by having a window of X seconds. private int timeSkewAllowance = 300; - @Autowired + // fetches and caches public keys for servers + @Autowired(required=false) private JWKSetCacheService validationServices; - // modular services to build out client filter + // creates JWT signer/validators for symmetric keys + @Autowired(required=false) + private SymmetricKeyJWTValidatorCacheService symmetricCacheService; + + // signer based on keypair for this client (for outgoing auth requests) + @Autowired(required=false) + private JWTSigningAndValidationService authenticationSignerService; + + @Autowired(required=false) + private HttpClient httpClient; + + /* + * Modular services to build out client filter. + */ + // looks at the request and determines which issuer to use for lookup on the server + private IssuerService issuerService; + // holds server information (auth URI, token URI, etc.), indexed by issuer private ServerConfigurationService servers; + // holds client information (client ID, redirect URI, etc.), indexed by issuer of the server private ClientConfigurationService clients; - private IssuerService issuerService; + // provides extra options to inject into the outbound request private AuthRequestOptionsService authOptions = new StaticAuthRequestOptionsService(); // initialize with an empty set of options + // builds the actual request URI based on input from all other services private AuthRequestUrlBuilder authRequestBuilder; - // private helper to handle target link URLs + // private helpers to handle target link URLs private TargetLinkURIAuthenticationSuccessHandler targetSuccessHandler = new TargetLinkURIAuthenticationSuccessHandler(); - + private TargetLinkURIChecker deepLinkFilter; + protected int httpSocketTimeout = HTTP_SOCKET_TIMEOUT; /** * OpenIdConnectAuthenticationFilter constructor */ - protected OIDCAuthenticationFilter() { + public OIDCAuthenticationFilter() { super(FILTER_PROCESSES_URL); targetSuccessHandler.passthrough = super.getSuccessHandler(); super.setAuthenticationSuccessHandler(targetSuccessHandler); @@ -115,13 +155,24 @@ protected OIDCAuthenticationFilter() { @Override public void afterPropertiesSet() { super.afterPropertiesSet(); + + // if our JOSE validators don't get wired in, drop defaults into place + + if (validationServices == null) { + validationServices = new JWKSetCacheService(); + } + + if (symmetricCacheService == null) { + symmetricCacheService = new SymmetricKeyJWTValidatorCacheService(); + } + } /* * This is the main entry point for the filter. - * + * * (non-Javadoc) - * + * * @see org.springframework.security.web.authentication. * AbstractAuthenticationProcessingFilter * #attemptAuthentication(javax.servlet.http.HttpServletRequest, @@ -154,7 +205,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ /** * Initiate an Authorization request - * + * * @param request * The request from which to extract parameters and perform the * authentication @@ -182,14 +233,12 @@ protected void handleAuthorizationRequest(HttpServletRequest request, HttpServle // there's a target URL in the response, we should save this so we can forward to it later session.setAttribute(TARGET_SESSION_VARIABLE, issResp.getTargetLinkUri()); } - + if (Strings.isNullOrEmpty(issuer)) { logger.error("No issuer found: " + issuer); throw new AuthenticationServiceException("No issuer found: " + issuer); } - session.setAttribute(ISSUER_SESSION_VARIABLE, issuer); - ServerConfiguration serverConfig = servers.getServerConfiguration(issuer); if (serverConfig == null) { logger.error("No server configuration found for issuer: " + issuer); @@ -197,6 +246,8 @@ protected void handleAuthorizationRequest(HttpServletRequest request, HttpServle } + session.setAttribute(ISSUER_SESSION_VARIABLE, serverConfig.getIssuer()); + RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig); if (clientConfig == null) { logger.error("No client configuration found for issuer: " + issuer); @@ -206,7 +257,7 @@ protected void handleAuthorizationRequest(HttpServletRequest request, HttpServle String redirectUri = null; if (clientConfig.getRegisteredRedirectUri() != null && clientConfig.getRegisteredRedirectUri().size() == 1) { // if there's a redirect uri configured (and only one), use that - redirectUri = clientConfig.getRegisteredRedirectUri().toArray(new String[] {})[0]; + redirectUri = Iterables.getOnlyElement(clientConfig.getRegisteredRedirectUri()); } else { // otherwise our redirect URI is this current URL, with no query parameters redirectUri = request.getRequestURL().toString(); @@ -221,7 +272,27 @@ protected void handleAuthorizationRequest(HttpServletRequest request, HttpServle Map options = authOptions.getOptions(serverConfig, clientConfig, request); - String authRequest = authRequestBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options); + // if we're using PKCE, handle the challenge here + if (clientConfig.getCodeChallengeMethod() != null) { + String codeVerifier = createCodeVerifier(session); + options.put("code_challenge_method", clientConfig.getCodeChallengeMethod().getName()); + if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.plain)) { + options.put("code_challenge", codeVerifier); + } else if (clientConfig.getCodeChallengeMethod().equals(PKCEAlgorithm.S256)) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + String hash = Base64URL.encode(digest.digest(codeVerifier.getBytes(StandardCharsets.US_ASCII))).toString(); + options.put("code_challenge", hash); + } catch (NoSuchAlgorithmException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + } + } + + String authRequest = authRequestBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, issResp.getLoginHint()); logger.debug("Auth Request: " + authRequest); @@ -244,11 +315,9 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ // check for state, if it doesn't match we bail early String storedState = getStoredState(session); - if (!Strings.isNullOrEmpty(storedState)) { - String state = request.getParameter("state"); - if (!storedState.equals(state)) { - throw new AuthenticationServiceException("State parameter mismatch on return. Expected " + storedState + " got " + state); - } + String requestState = request.getParameter("state"); + if (storedState == null || !storedState.equals(requestState)) { + throw new AuthenticationServiceException("State parameter mismatch on return. Expected " + storedState + " got " + requestState); } // look up the issuer that we set out to talk to @@ -258,9 +327,15 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ ServerConfiguration serverConfig = servers.getServerConfiguration(issuer); final RegisteredClient clientConfig = clients.getClientConfiguration(serverConfig); - MultiValueMap form = new LinkedMultiValueMap(); + MultiValueMap form = new LinkedMultiValueMap<>(); form.add("grant_type", "authorization_code"); form.add("code", authorizationCode); + form.setAll(authOptions.getTokenOptions(serverConfig, clientConfig, request)); + + String codeVerifier = getStoredCodeVerifier(session); + if (codeVerifier != null) { + form.add("code_verifier", codeVerifier); + } String redirectUri = getStoredSessionString(session, REDIRECT_URI_SESION_VARIABLE); if (redirectUri != null) { @@ -268,9 +343,15 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ } // Handle Token Endpoint interaction - HttpClient httpClient = new SystemDefaultHttpClient(); - httpClient.getParams().setParameter("http.socket.timeout", new Integer(httpSocketTimeout)); + if(httpClient == null) { + httpClient = HttpClientBuilder.create() + .useSystemProperties() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(httpSocketTimeout) + .build()) + .build(); + } HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); @@ -284,18 +365,76 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { ClientHttpRequest httpRequest = super.createRequest(url, method); httpRequest.getHeaders().add("Authorization", - String.format("Basic %s", Base64.encode(String.format("%s:%s", clientConfig.getClientId(), clientConfig.getClientSecret())) )); - - + String.format("Basic %s", Base64.encode(String.format("%s:%s", + UriUtils.encodePathSegment(clientConfig.getClientId(), "UTF-8"), + UriUtils.encodePathSegment(clientConfig.getClientSecret(), "UTF-8"))))); return httpRequest; } }; - } else { //Alternatively use form based auth + } else { + // we're not doing basic auth, figure out what other flavor we have restTemplate = new RestTemplate(factory); - form.add("client_id", clientConfig.getClientId()); - form.add("client_secret", clientConfig.getClientSecret()); + if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) || PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) { + // do a symmetric secret signed JWT for auth + + + JWTSigningAndValidationService signer = null; + JWSAlgorithm alg = clientConfig.getTokenEndpointAuthSigningAlg(); + + if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) && + (JWSAlgorithm.HS256.equals(alg) + || JWSAlgorithm.HS384.equals(alg) + || JWSAlgorithm.HS512.equals(alg))) { + + // generate one based on client secret + signer = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient()); + + } else if (PRIVATE_KEY.equals(clientConfig.getTokenEndpointAuthMethod())) { + + // needs to be wired in to the bean + signer = authenticationSignerService; + + if (alg == null) { + alg = authenticationSignerService.getDefaultSigningAlgorithm(); + } + } + + if (signer == null) { + throw new AuthenticationServiceException("Couldn't find required signer service for use with private key auth."); + } + + JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder(); + + claimsSet.issuer(clientConfig.getClientId()); + claimsSet.subject(clientConfig.getClientId()); + claimsSet.audience(Lists.newArrayList(serverConfig.getTokenEndpointUri())); + claimsSet.jwtID(UUID.randomUUID().toString()); + + // TODO: make this configurable + Date exp = new Date(System.currentTimeMillis() + (60 * 1000)); // auth good for 60 seconds + claimsSet.expirationTime(exp); + + Date now = new Date(System.currentTimeMillis()); + claimsSet.issueTime(now); + claimsSet.notBeforeTime(now); + + JWSHeader header = new JWSHeader(alg, null, null, null, null, null, null, null, null, null, + signer.getDefaultSignerKeyId(), + null, null); + SignedJWT jwt = new SignedJWT(header, claimsSet.build()); + + signer.signJwt(jwt, alg); + + form.add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"); + form.add("client_assertion", jwt.serialize()); + } else { + //Alternatively use form based auth + form.add("client_id", clientConfig.getClientId()); + form.add("client_secret", clientConfig.getClientSecret()); + } + } logger.debug("tokenEndpointURI = " + serverConfig.getTokenEndpointUri()); @@ -305,15 +444,13 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE try { jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class); - } catch (HttpClientErrorException httpClientErrorException) { + } catch (RestClientException e) { // Handle error - logger.error("Token Endpoint error response: " - + httpClientErrorException.getStatusText() + " : " - + httpClientErrorException.getMessage()); + logger.error("Token Endpoint error response: " + e.getMessage()); - throw new AuthenticationServiceException("Unable to obtain Access Token: " + httpClientErrorException.getMessage()); + throw new AuthenticationServiceException("Unable to obtain Access Token: " + e.getMessage()); } logger.debug("from TokenEndpoint jsonString = " + jsonString); @@ -363,21 +500,58 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE } try { - SignedJWT idToken = SignedJWT.parse(idTokenValue); + JWT idToken = JWTParser.parse(idTokenValue); // validate our ID Token over a number of tests - ReadOnlyJWTClaimsSet idClaims = idToken.getJWTClaimsSet(); + JWTClaimsSet idClaims = idToken.getJWTClaimsSet(); // check the signature - JwtSigningAndValidationService jwtValidator = validationServices.getValidator(serverConfig.getJwksUri()); - if (jwtValidator != null) { - if(!jwtValidator.validateSignature(idToken)) { - throw new AuthenticationServiceException("Signature validation failed"); + JWTSigningAndValidationService jwtValidator = null; + + Algorithm tokenAlg = idToken.getHeader().getAlgorithm(); + + Algorithm clientAlg = clientConfig.getIdTokenSignedResponseAlg(); + + if (clientAlg != null) { + if (!clientAlg.equals(tokenAlg)) { + throw new AuthenticationServiceException("Token algorithm " + tokenAlg + " does not match expected algorithm " + clientAlg); } - } else { - logger.info("No validation service found. Skipping signature validation"); } + if (idToken instanceof PlainJWT) { + + if (clientAlg == null) { + throw new AuthenticationServiceException("Unsigned ID tokens can only be used if explicitly configured in client."); + } + + if (tokenAlg != null && !tokenAlg.equals(Algorithm.NONE)) { + throw new AuthenticationServiceException("Unsigned token received, expected signature with " + tokenAlg); + } + } else if (idToken instanceof SignedJWT) { + + SignedJWT signedIdToken = (SignedJWT)idToken; + + if (tokenAlg.equals(JWSAlgorithm.HS256) + || tokenAlg.equals(JWSAlgorithm.HS384) + || tokenAlg.equals(JWSAlgorithm.HS512)) { + + // generate one based on client secret + jwtValidator = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient()); + } else { + // otherwise load from the server's public key + jwtValidator = validationServices.getValidator(serverConfig.getJwksUri()); + } + + if (jwtValidator != null) { + if(!jwtValidator.validateSignature(signedIdToken)) { + throw new AuthenticationServiceException("Signature validation failed"); + } + } else { + logger.error("No validation service found. Skipping signature validation"); + throw new AuthenticationServiceException("Unable to find an appropriate signature validator for ID Token."); + } + } // TODO: encrypted id tokens + // check the issuer if (idClaims.getIssuer() == null) { throw new AuthenticationServiceException("Id Token Issuer is null"); @@ -441,13 +615,11 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE + "ID Token to the session " + NONCE_SESSION_VARIABLE + " failed. Expected " + storedNonce + " got " + nonce + "."); } - // pull the subject (user id) out as a claim on the id_token + // construct an PendingOIDCAuthenticationToken and return a Authentication object w/the userId and the idToken - String userId = idClaims.getSubject(); - - // construct an OIDCAuthenticationToken and return a Authentication object w/the userId and the idToken - - OIDCAuthenticationToken token = new OIDCAuthenticationToken(userId, idClaims.getIssuer(), serverConfig, idTokenValue, accessTokenValue, refreshTokenValue); + PendingOIDCAuthenticationToken token = new PendingOIDCAuthenticationToken(idClaims.getSubject(), idClaims.getIssuer(), + serverConfig, + idToken, accessTokenValue, refreshTokenValue); Authentication authentication = this.getAuthenticationManager().authenticate(token); @@ -463,7 +635,7 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE /** * Handle Authorization Endpoint error - * + * * @param request * The request from which to extract parameters and handle the * error @@ -478,7 +650,7 @@ protected void handleError(HttpServletRequest request, HttpServletResponse respo String errorDescription = request.getParameter("error_description"); String errorURI = request.getParameter("error_uri"); - throw new AuthenticationServiceException("Error from Authorization Endpoint: " + error + " " + errorDescription + " " + errorURI); + throw new AuthorizationEndpointException(error, errorDescription, errorURI); } /** @@ -538,6 +710,26 @@ protected static String getStoredState(HttpSession session) { return getStoredSessionString(session, STATE_SESSION_VARIABLE); } + /** + * Create a random code challenge and store it in the session + * @param session + * @return + */ + protected static String createCodeVerifier(HttpSession session) { + String challenge = new BigInteger(50, new SecureRandom()).toString(16); + session.setAttribute(CODE_VERIFIER_SESSION_VARIABLE, challenge); + return challenge; + } + + /** + * Retrieve the stored challenge from our session + * @param session + * @return + */ + protected static String getStoredCodeVerifier(HttpSession session) { + return getStoredSessionString(session, CODE_VERIFIER_SESSION_VARIABLE); + } + @Override public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler successHandler) { @@ -556,20 +748,24 @@ public void setAuthenticationSuccessHandler(AuthenticationSuccessHandler success protected class TargetLinkURIAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private AuthenticationSuccessHandler passthrough; - + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) - throws IOException, ServletException { - + throws IOException, ServletException { + HttpSession session = request.getSession(); - + // check to see if we've got a target String target = getStoredSessionString(session, TARGET_SESSION_VARIABLE); - + if (!Strings.isNullOrEmpty(target)) { - // TODO (#547): should we (can we?) check to see if this URL is part of our app's namespace? session.removeAttribute(TARGET_SESSION_VARIABLE); + + if (deepLinkFilter != null) { + target = deepLinkFilter.filter(target); + } + response.sendRedirect(target); } else { // if the target was blank, use the default behavior here @@ -678,4 +874,29 @@ public void setAuthRequestOptionsService(AuthRequestOptionsService authOptions) this.authOptions = authOptions; } + public SymmetricKeyJWTValidatorCacheService getSymmetricCacheService() { + return symmetricCacheService; + } + + public void setSymmetricCacheService(SymmetricKeyJWTValidatorCacheService symmetricCacheService) { + this.symmetricCacheService = symmetricCacheService; + } + + public TargetLinkURIAuthenticationSuccessHandler getTargetLinkURIAuthenticationSuccessHandler() { + return targetSuccessHandler; + } + + public void setTargetLinkURIAuthenticationSuccessHandler( + TargetLinkURIAuthenticationSuccessHandler targetSuccessHandler) { + this.targetSuccessHandler = targetSuccessHandler; + } + + public TargetLinkURIChecker targetLinkURIChecker() { + return deepLinkFilter; + } + + public void setTargetLinkURIChecker(TargetLinkURIChecker deepLinkFilter) { + this.deepLinkFilter = deepLinkFilter; + } + } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationProvider.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationProvider.java index 68a5056bfd..b43b649df2 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationProvider.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationProvider.java @@ -1,100 +1,128 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client; import java.util.Collection; import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken; import org.mitre.openid.connect.model.UserInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UsernameNotFoundException; import com.google.common.base.Strings; -import com.google.common.collect.Lists; +import com.nimbusds.jwt.JWT; /** - * @author nemonik - * + * @author nemonik, Justin Richer + * */ public class OIDCAuthenticationProvider implements AuthenticationProvider { + private static Logger logger = LoggerFactory.getLogger(OIDCAuthenticationProvider.class); + private UserInfoFetcher userInfoFetcher = new UserInfoFetcher(); - private GrantedAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper(); + private OIDCAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper(); /* * (non-Javadoc) - * + * * @see org.springframework.security.authentication.AuthenticationProvider# * authenticate(org.springframework.security.core.Authentication) */ @Override - public Authentication authenticate(final Authentication authentication) - throws AuthenticationException { + public Authentication authenticate(final Authentication authentication) throws AuthenticationException { if (!supports(authentication.getClass())) { return null; } - if (authentication instanceof OIDCAuthenticationToken) { + if (authentication instanceof PendingOIDCAuthenticationToken) { - OIDCAuthenticationToken token = (OIDCAuthenticationToken) authentication; + PendingOIDCAuthenticationToken token = (PendingOIDCAuthenticationToken) authentication; - Collection authorities = Lists.newArrayList(new SubjectIssuerGrantedAuthority(token.getSub(), token.getIssuer())); + // get the ID Token value out + JWT idToken = token.getIdToken(); + // load the user info if we can UserInfo userInfo = userInfoFetcher.loadUserInfo(token); if (userInfo == null) { - // TODO: user Info not found -- error? + // user info not found -- could be an error, could be fine } else { + // if we found userinfo, double check it if (!Strings.isNullOrEmpty(userInfo.getSub()) && !userInfo.getSub().equals(token.getSub())) { // the userinfo came back and the user_id fields don't match what was in the id_token throw new UsernameNotFoundException("user_id mismatch between id_token and user_info call: " + token.getSub() + " / " + userInfo.getSub()); } } - return new OIDCAuthenticationToken(token.getSub(), - token.getIssuer(), - userInfo, authoritiesMapper.mapAuthorities(authorities), - token.getIdTokenValue(), token.getAccessTokenValue(), token.getRefreshTokenValue()); + return createAuthenticationToken(token, authoritiesMapper.mapAuthorities(idToken, userInfo), userInfo); } return null; } + /** + * Override this function to return a different kind of Authentication, processes the authorities differently, + * or do post-processing based on the UserInfo object. + * + * @param token + * @param authorities + * @param userInfo + * @return + */ + protected Authentication createAuthenticationToken(PendingOIDCAuthenticationToken token, Collection authorities, UserInfo userInfo) { + return new OIDCAuthenticationToken(token.getSub(), + token.getIssuer(), + userInfo, authorities, + token.getIdToken(), token.getAccessTokenValue(), token.getRefreshTokenValue()); + } + + /** + * @param userInfoFetcher + */ + public void setUserInfoFetcher(UserInfoFetcher userInfoFetcher) { + this.userInfoFetcher = userInfoFetcher; + } + /** * @param authoritiesMapper */ - public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { + public void setAuthoritiesMapper(OIDCAuthoritiesMapper authoritiesMapper) { this.authoritiesMapper = authoritiesMapper; } /* * (non-Javadoc) - * + * * @see * org.springframework.security.authentication.AuthenticationProvider#supports * (java.lang.Class) */ @Override public boolean supports(Class authentication) { - return OIDCAuthenticationToken.class.isAssignableFrom(authentication); + return PendingOIDCAuthenticationToken.class.isAssignableFrom(authentication); } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthoritiesMapper.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthoritiesMapper.java new file mode 100644 index 0000000000..0ee1d1c66e --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthoritiesMapper.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.client; + +import java.util.Collection; + +import org.mitre.openid.connect.model.UserInfo; +import org.springframework.security.core.GrantedAuthority; + +import com.nimbusds.jwt.JWT; + +/** + * @author jricher + * + */ +public interface OIDCAuthoritiesMapper { + + /** + * @param idToken the ID Token (parsed as a JWT, cannot be @null) + * @param userInfo userInfo of the current user (could be @null) + * @return the set of authorities to map to this user + */ + Collection mapAuthorities(JWT idToken, UserInfo userInfo); + +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/StaticPrefixTargetLinkURIChecker.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/StaticPrefixTargetLinkURIChecker.java new file mode 100644 index 0000000000..b7725bbe07 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/StaticPrefixTargetLinkURIChecker.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.openid.connect.client; + +/** + * Simple target URI checker, checks whether the string in question starts + * with a configured prefix. Returns "/" if the match fails. + * + * @author jricher + * + */ +public class StaticPrefixTargetLinkURIChecker implements TargetLinkURIChecker { + + private String prefix = ""; + + @Override + public String filter(String target) { + if (target == null) { + return "/"; + } else if (target.startsWith(prefix)) { + return target; + } else { + return "/"; + } + } + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/SubjectIssuerGrantedAuthority.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/SubjectIssuerGrantedAuthority.java index 01ffff0774..9d4c85c511 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/SubjectIssuerGrantedAuthority.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/SubjectIssuerGrantedAuthority.java @@ -1,21 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client; @@ -24,9 +23,9 @@ import com.google.common.base.Strings; /** - * + * * Simple authority representing a user at an issuer. - * + * * @author jricher * */ @@ -51,9 +50,9 @@ public SubjectIssuerGrantedAuthority(String subject, String issuer) { /** * Returns a string formed by concatenating the subject with the issuer, separated by _ and prepended with OIDC_ - * + * * For example, the user "bob" from issuer "http://id.example.com/" would return the authority string of: - * + * * OIDC_bob_http://id.example.com/ */ @Override diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/TargetLinkURIChecker.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/TargetLinkURIChecker.java new file mode 100644 index 0000000000..1fca0bfeb1 --- /dev/null +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/TargetLinkURIChecker.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.openid.connect.client; + +public interface TargetLinkURIChecker { + + /** + * Check the parameter to make sure that it's a valid deep-link into this application. + * + * @param target + * @return + */ + public String filter(String target); + +} diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java index 35c13f5d4a..5b755617bb 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/UserInfoFetcher.java @@ -1,84 +1,157 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.openid.connect.config.ServerConfiguration; +import org.mitre.openid.connect.config.ServerConfiguration.UserInfoTokenMethod; import org.mitre.openid.connect.model.DefaultUserInfo; -import org.mitre.openid.connect.model.OIDCAuthenticationToken; +import org.mitre.openid.connect.model.PendingOIDCAuthenticationToken; import org.mitre.openid.connect.model.UserInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** - * Utility class to fetch userinfo from the userinfo endpoint, if available. + * Utility class to fetch userinfo from the userinfo endpoint, if available. Caches the results. * @author jricher * */ public class UserInfoFetcher { - private Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class); + + private LoadingCache cache; - public UserInfo loadUserInfo(OIDCAuthenticationToken token) { + public UserInfoFetcher() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } - ServerConfiguration serverConfiguration = token.getServerConfiguration(); + public UserInfoFetcher(HttpClient httpClient) { + cache = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch + .maximumSize(100) + .build(new UserInfoLoader(httpClient)); + } - if (serverConfiguration == null) { - logger.warn("No server configuration found."); + public UserInfo loadUserInfo(final PendingOIDCAuthenticationToken token) { + try { + return cache.get(token); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Couldn't load User Info from token: " + e.getMessage()); return null; } - if (Strings.isNullOrEmpty(serverConfiguration.getUserInfoUri())) { - logger.warn("No userinfo endpoint, not fetching."); - return null; + } + + + private class UserInfoLoader extends CacheLoader { + private HttpComponentsClientHttpRequestFactory factory; + + UserInfoLoader(HttpClient httpClient) { + this.factory = new HttpComponentsClientHttpRequestFactory(httpClient); } - // if we got this far, try to actually get the userinfo - HttpClient httpClient = new SystemDefaultHttpClient(); + @Override + public UserInfo load(final PendingOIDCAuthenticationToken token) throws URISyntaxException { - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + ServerConfiguration serverConfiguration = token.getServerConfiguration(); - RestTemplate restTemplate = new RestTemplate(factory); + if (serverConfiguration == null) { + logger.warn("No server configuration found."); + return null; + } - MultiValueMap form = new LinkedMultiValueMap(); - form.add("access_token", token.getAccessTokenValue()); + if (Strings.isNullOrEmpty(serverConfiguration.getUserInfoUri())) { + logger.warn("No userinfo endpoint, not fetching."); + return null; + } - try { - String userInfoString = restTemplate.postForObject(serverConfiguration.getUserInfoUri(), form, String.class); + String userInfoString = null; - JsonObject userInfoJson = new JsonParser().parse(userInfoString).getAsJsonObject(); + if (serverConfiguration.getUserInfoTokenMethod() == null || serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.HEADER)) { + RestTemplate restTemplate = new RestTemplate(factory) { - UserInfo userInfo = DefaultUserInfo.fromJson(userInfoJson); + @Override + protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException { + ClientHttpRequest httpRequest = super.createRequest(url, method); + httpRequest.getHeaders().add("Authorization", String.format("Bearer %s", token.getAccessTokenValue())); + return httpRequest; + } + }; + + userInfoString = restTemplate.getForObject(serverConfiguration.getUserInfoUri(), String.class); + + } else if (serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.FORM)) { + MultiValueMap form = new LinkedMultiValueMap<>(); + form.add("access_token", token.getAccessTokenValue()); + + RestTemplate restTemplate = new RestTemplate(factory); + userInfoString = restTemplate.postForObject(serverConfiguration.getUserInfoUri(), form, String.class); + } else if (serverConfiguration.getUserInfoTokenMethod().equals(UserInfoTokenMethod.QUERY)) { + URIBuilder builder = new URIBuilder(serverConfiguration.getUserInfoUri()); + builder.setParameter("access_token", token.getAccessTokenValue()); + + RestTemplate restTemplate = new RestTemplate(factory); + userInfoString = restTemplate.getForObject(builder.toString(), String.class); + } - return userInfo; - } catch (Exception e) { - logger.warn("Error fetching userinfo", e); - return null; - } + if (!Strings.isNullOrEmpty(userInfoString)) { + + JsonObject userInfoJson = new JsonParser().parse(userInfoString).getAsJsonObject(); + + UserInfo userInfo = fromJson(userInfoJson); + + return userInfo; + } else { + // didn't get anything throw exception + throw new IllegalArgumentException("Unable to load user info"); + } + + } } + protected UserInfo fromJson(JsonObject userInfoJson) { + return DefaultUserInfo.fromJson(userInfoJson); + } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisher.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisher.java index 673a558832..dfd0eea85c 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisher.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisher.java @@ -1,26 +1,27 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.keypublisher; import java.util.Map; import java.util.UUID; -import org.mitre.jwt.signer.service.JwtSigningAndValidationService; -import org.mitre.openid.connect.view.JwkKeyListView; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.openid.connect.view.JWKSetView; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; @@ -37,13 +38,13 @@ */ public class ClientKeyPublisher implements BeanDefinitionRegistryPostProcessor { - private JwtSigningAndValidationService signingAndValidationService; + private JWTSigningAndValidationService signingAndValidationService; private String jwkPublishUrl; private BeanDefinitionRegistry registry; - private String jwkViewName = "jwkKeyList"; + private String jwkViewName = JWKSetView.VIEWNAME; /** * If the jwkPublishUrl field is set on this bean, set up a listener on that URL to publish keys. @@ -61,13 +62,13 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) clientKeyMapping.addPropertyValue("jwkPublishUrl", getJwkPublishUrl()); // randomize view name to make sure it doesn't conflict with local views - jwkViewName = "jwkKeyList-" + UUID.randomUUID().toString(); + jwkViewName = JWKSetView.VIEWNAME + "-" + UUID.randomUUID().toString(); viewResolver.addPropertyValue("jwkViewName", jwkViewName); // view bean - BeanDefinitionBuilder jwkView = BeanDefinitionBuilder.rootBeanDefinition(JwkKeyListView.class); - registry.registerBeanDefinition("jwkKeyList", jwkView.getBeanDefinition()); - viewResolver.addPropertyReference("jwk", "jwkKeyList"); + BeanDefinitionBuilder jwkView = BeanDefinitionBuilder.rootBeanDefinition(JWKSetView.class); + registry.registerBeanDefinition(JWKSetView.VIEWNAME, jwkView.getBeanDefinition()); + viewResolver.addPropertyReference("jwk", JWKSetView.VIEWNAME); } registry.registerBeanDefinition("clientKeyMapping", clientKeyMapping.getBeanDefinition()); @@ -114,14 +115,14 @@ public void setJwkPublishUrl(String jwkPublishUrl) { /** * @return the signingAndValidationService */ - public JwtSigningAndValidationService getSigningAndValidationService() { + public JWTSigningAndValidationService getSigningAndValidationService() { return signingAndValidationService; } /** * @param signingAndValidationService the signingAndValidationService to set */ - public void setSigningAndValidationService(JwtSigningAndValidationService signingAndValidationService) { + public void setSigningAndValidationService(JWTSigningAndValidationService signingAndValidationService) { this.signingAndValidationService = signingAndValidationService; } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisherMapping.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisherMapping.java index 55e59c503c..e601f3c271 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisherMapping.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/ClientKeyPublisherMapping.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.keypublisher; diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/JwkViewResolver.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/JwkViewResolver.java index d5999a9ce7..30ebdd63fd 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/JwkViewResolver.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/keypublisher/JwkViewResolver.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.keypublisher; @@ -26,9 +27,9 @@ import org.springframework.web.servlet.ViewResolver; /** - * + * * Simple view resolver to map JWK view names to appropriate beans - * + * * @author jricher * */ diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/model/IssuerServiceResponse.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/model/IssuerServiceResponse.java index 1355a71eff..e8de16a13d 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/model/IssuerServiceResponse.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/model/IssuerServiceResponse.java @@ -1,28 +1,29 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.model; /** - * + * * Data container to facilitate returns from the IssuerService API. - * + * * @author jricher * */ diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestOptionsService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestOptionsService.java index de651f63a1..73a8d377f1 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestOptionsService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestOptionsService.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; @@ -11,15 +28,34 @@ import org.mitre.openid.connect.config.ServerConfiguration; /** - * - * This service provides any extra options that need to be passed to the authentication request. + * + * This service provides any extra options that need to be passed to the authentication request, + * either through the authorization endpoint (getOptions) or the token endpoint (getTokenOptions). * These options may depend on the server configuration, client configuration, or HTTP request. - * + * * @author jricher * */ public interface AuthRequestOptionsService { + /** + * The set of options needed at the authorization endpoint. + * + * @param server + * @param client + * @param request + * @return + */ public Map getOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request); + /** + * The set of options needed at the token endpoint. + * + * @param server + * @param client + * @param request + * @return + */ + public Map getTokenOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request); + } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestUrlBuilder.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestUrlBuilder.java index 6a89a5fd50..14eb8a09ca 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestUrlBuilder.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/AuthRequestUrlBuilder.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; @@ -25,6 +26,8 @@ import org.mitre.openid.connect.config.ServerConfiguration; /** + * Builds a URL string to the IdP's authorization endpoint. + * * @author jricher * */ @@ -36,8 +39,9 @@ public interface AuthRequestUrlBuilder { * @param redirectUri * @param nonce * @param state + * @param loginHint * @return */ - public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options); + public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options, String loginHint); } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ClientConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ClientConfigurationService.java index e6c8eb8f62..6444376b3e 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ClientConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ClientConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/IssuerService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/IssuerService.java index b631d34a66..7e4e527024 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/IssuerService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/IssuerService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; @@ -24,9 +25,9 @@ import org.mitre.openid.connect.client.model.IssuerServiceResponse; /** - * + * * Gets an issuer for the given request. Might do dynamic discovery, or might be statically configured. - * + * * @author jricher * */ diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java index 3b56b376f6..0ca59bc103 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/RegisteredClientService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ServerConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ServerConfigurationService.java index c8cf10f860..44613fdc5e 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ServerConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/ServerConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java index 1b151d9057..2c32fd8fd9 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicRegistrationClientConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -24,7 +25,7 @@ import java.util.concurrent.ExecutionException; import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; import org.mitre.openid.connect.client.service.ClientConfigurationService; @@ -39,12 +40,15 @@ import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.gson.Gson; import com.google.gson.JsonObject; @@ -54,7 +58,10 @@ */ public class DynamicRegistrationClientConfigurationService implements ClientConfigurationService { - private static Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(DynamicRegistrationClientConfigurationService.class); private LoadingCache clients; @@ -62,26 +69,30 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf private RegisteredClient template; - private Set whitelist = new HashSet(); - private Set blacklist = new HashSet(); + private Set whitelist = new HashSet<>(); + private Set blacklist = new HashSet<>(); public DynamicRegistrationClientConfigurationService() { - clients = CacheBuilder.newBuilder().build(new DynamicClientRegistrationLoader()); + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public DynamicRegistrationClientConfigurationService(HttpClient httpClient) { + clients = CacheBuilder.newBuilder().build(new DynamicClientRegistrationLoader(httpClient)); } @Override public RegisteredClient getClientConfiguration(ServerConfiguration issuer) { try { - if (!whitelist.isEmpty() && !whitelist.contains(issuer)) { + if (!whitelist.isEmpty() && !whitelist.contains(issuer.getIssuer())) { throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + issuer); } - if (blacklist.contains(issuer)) { + if (blacklist.contains(issuer.getIssuer())) { throw new AuthenticationServiceException("Issuer was in blacklist: " + issuer); } return clients.get(issuer); - } catch (ExecutionException e) { + } catch (UncheckedExecutionException | ExecutionException e) { logger.warn("Unable to get client configuration", e); return null; } @@ -154,18 +165,25 @@ public void setBlacklist(Set blacklist) { /** * Loader class that fetches the client information. - * + * * If a client has been registered (ie, it's known to the RegisteredClientService), then this * will fetch the client's configuration from the server. - * + * * @author jricher * */ public class DynamicClientRegistrationLoader extends CacheLoader { - private HttpClient httpClient = new SystemDefaultHttpClient(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + private HttpComponentsClientHttpRequestFactory httpFactory; private Gson gson = new Gson(); // note that this doesn't serialize nulls by default + public DynamicClientRegistrationLoader() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public DynamicClientRegistrationLoader(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + } + @Override public RegisteredClient load(ServerConfiguration serverConfig) throws Exception { RestTemplate restTemplate = new RestTemplate(httpFactory); @@ -182,32 +200,45 @@ public RegisteredClient load(ServerConfiguration serverConfig) throws Exception headers.setContentType(MediaType.APPLICATION_JSON); headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); - HttpEntity entity = new HttpEntity(serializedClient, headers); + HttpEntity entity = new HttpEntity<>(serializedClient, headers); - String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class); - // TODO: handle HTTP errors + try { + String registered = restTemplate.postForObject(serverConfig.getRegistrationEndpointUri(), entity, String.class); - RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); + RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); - // save this client for later - registeredClientService.save(serverConfig.getIssuer(), client); + // save this client for later + registeredClientService.save(serverConfig.getIssuer(), client); - return client; + return client; + } catch (RestClientException rce) { + throw new InvalidClientException("Error registering client with server"); + } } else { - // load this client's information from the server - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, knownClient.getRegistrationAccessToken())); - headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); + if (knownClient.getClientId() == null) { + + // load this client's information from the server + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, knownClient.getRegistrationAccessToken())); + headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON)); - HttpEntity entity = new HttpEntity(headers); + HttpEntity entity = new HttpEntity<>(headers); - String registered = restTemplate.exchange(knownClient.getRegistrationClientUri(), HttpMethod.GET, entity, String.class).getBody(); - // TODO: handle HTTP errors + try { + String registered = restTemplate.exchange(knownClient.getRegistrationClientUri(), HttpMethod.GET, entity, String.class).getBody(); + // TODO: handle HTTP errors - RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); + RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(registered); - return client; + return client; + } catch (RestClientException rce) { + throw new InvalidClientException("Error loading previously registered client information from server"); + } + } else { + // it's got a client ID from the store, don't bother trying to load it + return knownClient; + } } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java index 584488e6ce..5f451c2dcb 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/DynamicServerConfigurationService.java @@ -1,37 +1,38 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; -import static org.mitre.discovery.util.JsonUtils.getAsBoolean; -import static org.mitre.discovery.util.JsonUtils.getAsEncryptionMethodList; -import static org.mitre.discovery.util.JsonUtils.getAsJweAlgorithmList; -import static org.mitre.discovery.util.JsonUtils.getAsJwsAlgorithmList; -import static org.mitre.discovery.util.JsonUtils.getAsString; -import static org.mitre.discovery.util.JsonUtils.getAsStringList; +import static org.mitre.util.JsonUtils.getAsBoolean; +import static org.mitre.util.JsonUtils.getAsEncryptionMethodList; +import static org.mitre.util.JsonUtils.getAsJweAlgorithmList; +import static org.mitre.util.JsonUtils.getAsJwsAlgorithmList; +import static org.mitre.util.JsonUtils.getAsString; +import static org.mitre.util.JsonUtils.getAsStringList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutionException; import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.openid.connect.client.service.ServerConfigurationService; import org.mitre.openid.connect.config.ServerConfiguration; import org.slf4j.Logger; @@ -43,37 +44,38 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import static org.mitre.discovery.util.JsonUtils.getAsBoolean; -import static org.mitre.discovery.util.JsonUtils.getAsEncryptionMethodList; -import static org.mitre.discovery.util.JsonUtils.getAsJweAlgorithmList; -import static org.mitre.discovery.util.JsonUtils.getAsJwsAlgorithmList; -import static org.mitre.discovery.util.JsonUtils.getAsString; -import static org.mitre.discovery.util.JsonUtils.getAsStringList; - /** - * + * * Dynamically fetches OpenID Connect server configurations based on the issuer. Caches the server configurations. - * + * * @author jricher * */ public class DynamicServerConfigurationService implements ServerConfigurationService { - private static Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class); // map of issuer -> server configuration, loaded dynamically from service discovery private LoadingCache servers; - private Set whitelist = new HashSet(); - private Set blacklist = new HashSet(); + private Set whitelist = new HashSet<>(); + private Set blacklist = new HashSet<>(); public DynamicServerConfigurationService() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public DynamicServerConfigurationService(HttpClient httpClient) { // initialize the cache - servers = CacheBuilder.newBuilder().build(new OpenIDConnectServiceConfigurationFetcher()); + servers = CacheBuilder.newBuilder().build(new OpenIDConnectServiceConfigurationFetcher(httpClient)); } /** @@ -117,8 +119,8 @@ public ServerConfiguration getServerConfiguration(String issuer) { } return servers.get(issuer); - } catch (ExecutionException e) { - logger.warn("Couldn't load configuration for " + issuer, e); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Couldn't load configuration for " + issuer + ": " + e); return null; } @@ -129,10 +131,13 @@ public ServerConfiguration getServerConfiguration(String issuer) { * */ private class OpenIDConnectServiceConfigurationFetcher extends CacheLoader { - private HttpClient httpClient = new SystemDefaultHttpClient(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + private HttpComponentsClientHttpRequestFactory httpFactory; private JsonParser parser = new JsonParser(); + OpenIDConnectServiceConfigurationFetcher(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + } + @Override public ServerConfiguration load(String issuer) throws Exception { RestTemplate restTemplate = new RestTemplate(httpFactory); @@ -157,7 +162,7 @@ public ServerConfiguration load(String issuer) throws Exception { } if (!issuer.equals(o.get("issuer").getAsString())) { - throw new IllegalStateException("Discovered issuers didn't match, expected " + issuer + " got " + o.get("issuer").getAsString()); + logger.info("Issuer used for discover was " + issuer + " but final issuer is " + o.get("issuer").getAsString()); } conf.setIssuer(o.get("issuer").getAsString()); diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/EncryptedAuthRequestUrlBuilder.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/EncryptedAuthRequestUrlBuilder.java index 1d96fc877b..cad7d7399a 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/EncryptedAuthRequestUrlBuilder.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/EncryptedAuthRequestUrlBuilder.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -8,7 +25,7 @@ import java.util.Map.Entry; import org.apache.http.client.utils.URIBuilder; -import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService; +import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; import org.mitre.jwt.signer.service.impl.JWKSetCacheService; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder; @@ -16,6 +33,7 @@ import org.springframework.security.authentication.AuthenticationServiceException; import com.google.common.base.Joiner; +import com.google.common.base.Strings; import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWEHeader; @@ -38,33 +56,38 @@ public class EncryptedAuthRequestUrlBuilder implements AuthRequestUrlBuilder { * @see org.mitre.openid.connect.client.service.AuthRequestUrlBuilder#buildAuthRequestUrl(org.mitre.openid.connect.config.ServerConfiguration, org.mitre.oauth2.model.RegisteredClient, java.lang.String, java.lang.String, java.lang.String, java.util.Map) */ @Override - public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options) { + public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options, String loginHint) { // create our signed JWT for the request object - JWTClaimsSet claims = new JWTClaimsSet(); + JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder(); //set parameters to JwtClaims - claims.setClaim("response_type", "code"); - claims.setClaim("client_id", clientConfig.getClientId()); - claims.setClaim("scope", Joiner.on(" ").join(clientConfig.getScope())); + claims.claim("response_type", "code"); + claims.claim("client_id", clientConfig.getClientId()); + claims.claim("scope", Joiner.on(" ").join(clientConfig.getScope())); // build our redirect URI - claims.setClaim("redirect_uri", redirectUri); + claims.claim("redirect_uri", redirectUri); // this comes back in the id token - claims.setClaim("nonce", nonce); + claims.claim("nonce", nonce); // this comes back in the auth request return - claims.setClaim("state", state); + claims.claim("state", state); // Optional parameters for (Entry option : options.entrySet()) { - claims.setClaim(option.getKey(), option.getValue()); + claims.claim(option.getKey(), option.getValue()); + } + + // if there's a login hint, send it + if (!Strings.isNullOrEmpty(loginHint)) { + claims.claim("login_hint", loginHint); } - EncryptedJWT jwt = new EncryptedJWT(new JWEHeader(alg, enc), claims); + EncryptedJWT jwt = new EncryptedJWT(new JWEHeader(alg, enc), claims.build()); - JwtEncryptionAndDecryptionService encryptor = encrypterService.getEncrypter(serverConfig.getJwksUri()); + JWTEncryptionAndDecryptionService encryptor = encrypterService.getEncrypter(serverConfig.getJwksUri()); encryptor.encryptJwt(jwt); diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridClientConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridClientConfigurationService.java index 940263c325..16fed24ed3 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridClientConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridClientConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -31,12 +32,12 @@ * Houses both a static client configuration and a dynamic client configuration * service in one object. Checks the static service first, then falls through to * the dynamic service. - * + * * Provides configuration passthrough for the template, registered client service, whitelist, * and blacklist for the dynamic service, and to the static service's client map. - * + * * @author jricher - * + * */ public class HybridClientConfigurationService implements ClientConfigurationService { diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridIssuerService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridIssuerService.java index f7848bc82c..816f03698e 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridIssuerService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridIssuerService.java @@ -1,3 +1,20 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; import java.util.Set; @@ -10,16 +27,48 @@ import com.google.common.collect.Sets; /** - * + * * Issuer service that tries to parse input from the inputs from a third-party * account chooser service (if possible), but falls back to webfinger discovery * if not. - * + * * @author jricher * */ public class HybridIssuerService implements IssuerService { + /** + * @return + * @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#getAccountChooserUrl() + */ + public String getAccountChooserUrl() { + return thirdPartyIssuerService.getAccountChooserUrl(); + } + + /** + * @param accountChooserUrl + * @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#setAccountChooserUrl(java.lang.String) + */ + public void setAccountChooserUrl(String accountChooserUrl) { + thirdPartyIssuerService.setAccountChooserUrl(accountChooserUrl); + } + + /** + * @return + * @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#isForceHttps() + */ + public boolean isForceHttps() { + return webfingerIssuerService.isForceHttps(); + } + + /** + * @param forceHttps + * @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#setForceHttps(boolean) + */ + public void setForceHttps(boolean forceHttps) { + webfingerIssuerService.setForceHttps(forceHttps); + } + private ThirdPartyIssuerService thirdPartyIssuerService = new ThirdPartyIssuerService(); private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService(); diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridServerConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridServerConfigurationService.java index 2a01339e07..cf519442ca 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridServerConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/HybridServerConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -29,11 +30,11 @@ * Houses both a static server configuration and a dynamic server configuration * service in one object. Checks the static service first, then falls through to * the dynamic service. - * + * * Provides configuration passthrough to the dynamic service's whitelist and blacklist, * and to the static service's server map. - * - * + * + * * @author jricher * */ diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java index 315fe47fda..6be9eca8e7 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/InMemoryRegisteredClientService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -31,7 +32,7 @@ */ public class InMemoryRegisteredClientService implements RegisteredClientService { - private Map clients = new HashMap(); + private Map clients = new HashMap<>(); /* (non-Javadoc) * @see org.mitre.openid.connect.client.service.RegisteredClientService#getByIssuer(java.lang.String) diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java index 0d7d3c0361..de69bb8f3e 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/JsonFileRegisteredClientService.java @@ -1,35 +1,35 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.Type; -import java.util.Date; import java.util.HashMap; import java.util.Map; import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; import org.mitre.openid.connect.client.service.RegisteredClientService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +40,6 @@ import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; @@ -51,49 +50,30 @@ */ public class JsonFileRegisteredClientService implements RegisteredClientService { - private static Logger logger = LoggerFactory.getLogger(JsonFileRegisteredClientService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(JsonFileRegisteredClientService.class); private Gson gson = new GsonBuilder() - .registerTypeAdapter(RegisteredClient.class, new JsonSerializer() { - @Override - public JsonElement serialize(RegisteredClient src, Type typeOfSrc, JsonSerializationContext context) { - JsonObject obj = new JsonObject(); - obj.addProperty("token", src.getRegistrationAccessToken()); - obj.addProperty("uri", src.getRegistrationClientUri()); - if (src.getClientIdIssuedAt() != null) { - obj.addProperty("issued", src.getClientIdIssuedAt().getTime()); - } - if (src.getClientSecretExpiresAt() != null) { - obj.addProperty("expires", src.getClientSecretExpiresAt().getTime()); - } - return obj; - } - }) - .registerTypeAdapter(RegisteredClient.class, new JsonDeserializer() { - @Override - public RegisteredClient deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonObject()) { - JsonObject src = json.getAsJsonObject(); - RegisteredClient rc = new RegisteredClient(); - rc.setRegistrationAccessToken(src.get("token").getAsString()); - rc.setRegistrationClientUri(src.get("uri").getAsString()); - if (src.has("issued") && !src.get("issued").isJsonNull()) { - rc.setClientIdIssuedAt(new Date(src.get("issued").getAsLong())); + .registerTypeAdapter(RegisteredClient.class, new JsonSerializer() { + @Override + public JsonElement serialize(RegisteredClient src, Type typeOfSrc, JsonSerializationContext context) { + return ClientDetailsEntityJsonProcessor.serialize(src); } - if (src.has("expires") && !src.get("expires").isJsonNull()) { - rc.setClientSecretExpiresAt(new Date(src.get("expires").getAsLong())); + }) + .registerTypeAdapter(RegisteredClient.class, new JsonDeserializer() { + @Override + public RegisteredClient deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return ClientDetailsEntityJsonProcessor.parseRegistered(json); } - return rc; - } else { - return null; - } - } - }) - .create(); + }) + .setPrettyPrinting() + .create(); private File file; - private Map clients = new HashMap(); + private Map clients = new HashMap<>(); public JsonFileRegisteredClientService(String filename) { this.file = new File(filename); @@ -120,6 +100,7 @@ public void save(String issuer, RegisteredClient client) { /** * Sync the map of clients out to disk. */ + @SuppressWarnings("serial") private void write() { try { if (!file.exists()) { @@ -133,8 +114,6 @@ private void write() { out.close(); - } catch (FileNotFoundException e) { - logger.error("Could not write to output file", e); } catch (IOException e) { logger.error("Could not write to output file", e); } @@ -143,6 +122,7 @@ private void write() { /** * Load the map in from disk. */ + @SuppressWarnings("serial") private void load() { try { if (!file.exists()) { @@ -155,8 +135,6 @@ private void load() { in.close(); - } catch (FileNotFoundException e) { - logger.error("Could not read from input file", e); } catch (IOException e) { logger.error("Could not read from input file", e); } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/PlainAuthRequestUrlBuilder.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/PlainAuthRequestUrlBuilder.java index 7c4cccc776..86ecece0ef 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/PlainAuthRequestUrlBuilder.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/PlainAuthRequestUrlBuilder.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -30,11 +31,12 @@ import org.springframework.security.authentication.AuthenticationServiceException; import com.google.common.base.Joiner; +import com.google.common.base.Strings; /** - * + * * Builds an auth request redirect URI with normal query parameters. - * + * * @author jricher * */ @@ -44,7 +46,7 @@ public class PlainAuthRequestUrlBuilder implements AuthRequestUrlBuilder { * @see org.mitre.openid.connect.client.service.AuthRequestUrlBuilder#buildAuthRequest(javax.servlet.http.HttpServletRequest, org.mitre.openid.connect.config.ServerConfiguration, org.springframework.security.oauth2.provider.ClientDetails) */ @Override - public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options) { + public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options, String loginHint) { try { URIBuilder uriBuilder = new URIBuilder(serverConfig.getAuthorizationEndpointUri()); @@ -63,6 +65,11 @@ public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredCl uriBuilder.addParameter(option.getKey(), option.getValue()); } + // if there's a login hint, send it + if (!Strings.isNullOrEmpty(loginHint)) { + uriBuilder.addParameter("login_hint", loginHint); + } + return uriBuilder.build().toString(); } catch (URISyntaxException e) { diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/SignedAuthRequestUrlBuilder.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/SignedAuthRequestUrlBuilder.java index 78fcd9bd7e..604a72a391 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/SignedAuthRequestUrlBuilder.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/SignedAuthRequestUrlBuilder.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -24,13 +25,15 @@ import java.util.Map.Entry; import org.apache.http.client.utils.URIBuilder; -import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.client.service.AuthRequestUrlBuilder; import org.mitre.openid.connect.config.ServerConfiguration; import org.springframework.security.authentication.AuthenticationServiceException; import com.google.common.base.Joiner; +import com.google.common.base.Strings; +import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -41,41 +44,49 @@ */ public class SignedAuthRequestUrlBuilder implements AuthRequestUrlBuilder { - private JwtSigningAndValidationService signingAndValidationService; + private JWTSigningAndValidationService signingAndValidationService; /* (non-Javadoc) * @see org.mitre.openid.connect.client.service.AuthRequestUrlBuilder#buildAuthRequestUrl(org.mitre.openid.connect.config.ServerConfiguration, org.springframework.security.oauth2.provider.ClientDetails, java.lang.String, java.lang.String, java.lang.String) */ @Override - public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options) { + public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredClient clientConfig, String redirectUri, String nonce, String state, Map options, String loginHint) { // create our signed JWT for the request object - JWTClaimsSet claims = new JWTClaimsSet(); + JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder(); //set parameters to JwtClaims - claims.setClaim("response_type", "code"); - claims.setClaim("client_id", clientConfig.getClientId()); - claims.setClaim("scope", Joiner.on(" ").join(clientConfig.getScope())); + claims.claim("response_type", "code"); + claims.claim("client_id", clientConfig.getClientId()); + claims.claim("scope", Joiner.on(" ").join(clientConfig.getScope())); // build our redirect URI - claims.setClaim("redirect_uri", redirectUri); + claims.claim("redirect_uri", redirectUri); // this comes back in the id token - claims.setClaim("nonce", nonce); + claims.claim("nonce", nonce); // this comes back in the auth request return - claims.setClaim("state", state); + claims.claim("state", state); // Optional parameters for (Entry option : options.entrySet()) { - claims.setClaim(option.getKey(), option.getValue()); + claims.claim(option.getKey(), option.getValue()); } + // if there's a login hint, send it + if (!Strings.isNullOrEmpty(loginHint)) { + claims.claim("login_hint", loginHint); + } + JWSAlgorithm alg = clientConfig.getRequestObjectSigningAlg(); + if (alg == null) { + alg = signingAndValidationService.getDefaultSigningAlgorithm(); + } - SignedJWT jwt = new SignedJWT(new JWSHeader(signingAndValidationService.getDefaultSigningAlgorithm()), claims); + SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claims.build()); - signingAndValidationService.signJwt(jwt); + signingAndValidationService.signJwt(jwt, alg); try { URIBuilder uriBuilder = new URIBuilder(serverConfig.getAuthorizationEndpointUri()); @@ -91,14 +102,14 @@ public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredCl /** * @return the signingAndValidationService */ - public JwtSigningAndValidationService getSigningAndValidationService() { + public JWTSigningAndValidationService getSigningAndValidationService() { return signingAndValidationService; } /** * @param signingAndValidationService the signingAndValidationService to set */ - public void setSigningAndValidationService(JwtSigningAndValidationService signingAndValidationService) { + public void setSigningAndValidationService(JWTSigningAndValidationService signingAndValidationService) { this.signingAndValidationService = signingAndValidationService; } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticAuthRequestOptionsService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticAuthRequestOptionsService.java index 05da21d4a0..8febc64a09 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticAuthRequestOptionsService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticAuthRequestOptionsService.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -13,15 +30,16 @@ import org.mitre.openid.connect.config.ServerConfiguration; /** - * + * * Always returns the same set of options. - * + * * @author jricher * */ public class StaticAuthRequestOptionsService implements AuthRequestOptionsService { - private Map options = new HashMap(); + private Map options = new HashMap<>(); + private Map tokenOptions = new HashMap<>(); /* (non-Javadoc) * @see org.mitre.openid.connect.client.service.AuthRequestOptionsService#getOptions(org.mitre.openid.connect.config.ServerConfiguration, org.mitre.oauth2.model.RegisteredClient, javax.servlet.http.HttpServletRequest) @@ -31,8 +49,16 @@ public Map getOptions(ServerConfiguration server, RegisteredClie return options; } + /* (non-Javadoc) + * @see org.mitre.openid.connect.client.service.AuthRequestOptionsService#getTokenOptions(org.mitre.openid.connect.config.ServerConfiguration, org.mitre.oauth2.model.RegisteredClient, javax.servlet.http.HttpServletRequest) + */ + @Override + public Map getTokenOptions(ServerConfiguration server, RegisteredClient client, HttpServletRequest request) { + return tokenOptions; + } + /** - * @return the options + * @return the options object directly */ public Map getOptions() { return options; @@ -45,6 +71,18 @@ public void setOptions(Map options) { this.options = options; } + /** + * @return the tokenOptions + */ + public Map getTokenOptions() { + return tokenOptions; + } + /** + * @param tokenOptions the tokenOptions to set + */ + public void setTokenOptions(Map tokenOptions) { + this.tokenOptions = tokenOptions; + } } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticClientConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticClientConfigurationService.java index 8b1422ad94..df31018047 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticClientConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticClientConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -29,9 +30,9 @@ /** * Client configuration service that holds a static map from issuer URL to a ClientDetails object to use at that issuer. - * + * * Designed to be configured as a bean. - * + * * @author jricher * */ @@ -56,7 +57,7 @@ public void setClients(Map clients) { /** * Get the client configured for this issuer - * + * * @see org.mitre.openid.connect.client.service.ClientConfigurationService#getClientConfiguration(java.lang.String) */ @Override @@ -66,7 +67,7 @@ public RegisteredClient getClientConfiguration(ServerConfiguration issuer) { } @PostConstruct - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { if (clients == null || clients.isEmpty()) { throw new IllegalArgumentException("Clients map cannot be null or empty"); } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticServerConfigurationService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticServerConfigurationService.java index 3ead65c284..ebca40c1e4 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticServerConfigurationService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticServerConfigurationService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -28,7 +29,7 @@ /** * Statically configured server configuration service that maps issuer URLs to server configurations to use at that issuer. - * + * * @author jricher * */ @@ -60,7 +61,7 @@ public ServerConfiguration getServerConfiguration(String issuer) { } @PostConstruct - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { if (servers == null || servers.isEmpty()) { throw new IllegalArgumentException("Servers map cannot be null or empty."); } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticSingleIssuerService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticSingleIssuerService.java index 7d9cf8ab92..c72b655236 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticSingleIssuerService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/StaticSingleIssuerService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -51,7 +52,7 @@ public void setIssuer(String issuer) { /** * Always returns the configured issuer URL - * + * * @see org.mitre.openid.connect.client.service.IssuerService#getIssuer(javax.servlet.http.HttpServletRequest) */ @Override @@ -60,7 +61,7 @@ public IssuerServiceResponse getIssuer(HttpServletRequest request) { } @PostConstruct - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { if (Strings.isNullOrEmpty(issuer)) { throw new IllegalArgumentException("Issuer must not be null or empty."); diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/ThirdPartyIssuerService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/ThirdPartyIssuerService.java index e6de8f4191..b26b91c897 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/ThirdPartyIssuerService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/ThirdPartyIssuerService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -34,9 +35,9 @@ import com.google.common.base.Strings; /** - * + * * Determines the issuer using an account chooser or other third-party-initiated login - * + * * @author jricher * */ @@ -44,8 +45,8 @@ public class ThirdPartyIssuerService implements IssuerService { private String accountChooserUrl; - private Set whitelist = new HashSet(); - private Set blacklist = new HashSet(); + private Set whitelist = new HashSet<>(); + private Set blacklist = new HashSet<>(); /* (non-Javadoc) * @see org.mitre.openid.connect.client.service.IssuerService#getIssuer(javax.servlet.http.HttpServletRequest) @@ -127,11 +128,8 @@ public void setBlacklist(Set blacklist) { this.blacklist = blacklist; } - /* (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ @PostConstruct - public void afterPropertiesSet() throws Exception { + public void afterPropertiesSet() { if (Strings.isNullOrEmpty(this.accountChooserUrl)) { throw new IllegalArgumentException("Account Chooser URL cannot be null or empty"); } diff --git a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/WebfingerIssuerService.java b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/WebfingerIssuerService.java index f0ee0d6fe1..ca2fe59494 100644 --- a/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/WebfingerIssuerService.java +++ b/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/WebfingerIssuerService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -27,7 +28,7 @@ import org.apache.http.client.HttpClient; import org.apache.http.client.utils.URIBuilder; -import org.apache.http.impl.client.SystemDefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.discovery.util.WebfingerURLNormalizer; import org.mitre.openid.connect.client.model.IssuerServiceResponse; import org.mitre.openid.connect.client.service.IssuerService; @@ -35,6 +36,7 @@ import org.slf4j.LoggerFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponents; @@ -42,9 +44,11 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; import com.google.gson.JsonParser; /** @@ -54,13 +58,26 @@ */ public class WebfingerIssuerService implements IssuerService { - private static Logger logger = LoggerFactory.getLogger(WebfingerIssuerService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(WebfingerIssuerService.class); // map of user input -> issuer, loaded dynamically from webfinger discover - private LoadingCache issuers; + private LoadingCache issuers; + + // private data shuttle class to get back two bits of info from the cache loader + private class LoadingResult { + public String loginHint; + public String issuer; + public LoadingResult(String loginHint, String issuer) { + this.loginHint = loginHint; + this.issuer = issuer; + } + } - private Set whitelist = new HashSet(); - private Set blacklist = new HashSet(); + private Set whitelist = new HashSet<>(); + private Set blacklist = new HashSet<>(); /** * Name of the incoming parameter to check for discovery purposes. @@ -72,8 +89,17 @@ public class WebfingerIssuerService implements IssuerService { */ private String loginPageUrl; + /** + * Strict enfocement of "https" + */ + private boolean forceHttps = true; + public WebfingerIssuerService() { - issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher()); + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public WebfingerIssuerService(HttpClient httpClient) { + issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher(httpClient)); } /* (non-Javadoc) @@ -85,18 +111,18 @@ public IssuerServiceResponse getIssuer(HttpServletRequest request) { String identifier = request.getParameter(parameterName); if (!Strings.isNullOrEmpty(identifier)) { try { - String issuer = issuers.get(WebfingerURLNormalizer.normalizeResource(identifier)); - if (!whitelist.isEmpty() && !whitelist.contains(issuer)) { - throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + issuer); + LoadingResult lr = issuers.get(identifier); + if (!whitelist.isEmpty() && !whitelist.contains(lr.issuer)) { + throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + lr.issuer); } - if (blacklist.contains(issuer)) { - throw new AuthenticationServiceException("Issuer was in blacklist: " + issuer); + if (blacklist.contains(lr.issuer)) { + throw new AuthenticationServiceException("Issuer was in blacklist: " + lr.issuer); } - return new IssuerServiceResponse(issuer, null, null); - } catch (ExecutionException e) { - logger.warn("Issue fetching issuer for user input: " + identifier, e); + return new IssuerServiceResponse(lr.issuer, lr.loginHint, request.getParameter("target_link_uri")); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Issue fetching issuer for user input: " + identifier + ": " + e.getMessage()); return null; } @@ -163,27 +189,52 @@ public void setBlacklist(Set blacklist) { this.blacklist = blacklist; } + /** + * @return the forceHttps + */ + public boolean isForceHttps() { + return forceHttps; + } + + /** + * @param forceHttps the forceHttps to set + */ + public void setForceHttps(boolean forceHttps) { + this.forceHttps = forceHttps; + } + /** * @author jricher * */ - private class WebfingerIssuerFetcher extends CacheLoader { - private HttpClient httpClient = new SystemDefaultHttpClient(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + private class WebfingerIssuerFetcher extends CacheLoader { + private HttpComponentsClientHttpRequestFactory httpFactory; private JsonParser parser = new JsonParser(); + WebfingerIssuerFetcher(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + } + @Override - public String load(UriComponents key) throws Exception { + public LoadingResult load(String identifier) throws Exception { + + UriComponents key = WebfingerURLNormalizer.normalizeResource(identifier); RestTemplate restTemplate = new RestTemplate(httpFactory); // construct the URL to go to - // preserving http scheme is strictly for demo system use only. String scheme = key.getScheme(); - if (!Strings.isNullOrEmpty(scheme) && scheme.equals("http")) { - scheme = "http://"; // add on colon and slashes. - logger.warn("Webfinger endpoint MUST use the https URI scheme."); + + // preserving http scheme is strictly for demo system use only. + if (!Strings.isNullOrEmpty(scheme) &&scheme.equals("http")) { + if (forceHttps) { + throw new IllegalArgumentException("Scheme must not be 'http'"); + } else { + logger.warn("Webfinger endpoint MUST use the https URI scheme, overriding by configuration"); + scheme = "http://"; // add on colon and slashes. + } } else { + // otherwise we don't know the scheme, assume HTTPS scheme = "https://"; } @@ -195,46 +246,56 @@ public String load(UriComponents key) throws Exception { + "/.well-known/webfinger" + (Strings.isNullOrEmpty(key.getQuery()) ? "" : "?" + key.getQuery()) ); - builder.addParameter("resource", key.toString()); + builder.addParameter("resource", identifier); builder.addParameter("rel", "http://openid.net/specs/connect/1.0/issuer"); - // do the fetch - logger.info("Loading: " + builder.toString()); - String webfingerResponse = restTemplate.getForObject(builder.build(), String.class); - - // TODO: catch and handle HTTP errors - - JsonElement json = parser.parse(webfingerResponse); - - // TODO: catch and handle JSON errors - - if (json != null && json.isJsonObject()) { - // find the issuer - JsonArray links = json.getAsJsonObject().get("links").getAsJsonArray(); - for (JsonElement link : links) { - if (link.isJsonObject()) { - JsonObject linkObj = link.getAsJsonObject(); - if (linkObj.has("href") - && linkObj.has("rel") - && linkObj.get("rel").getAsString().equals("http://openid.net/specs/connect/1.0/issuer")) { + try { - // we found the issuer, return it - return linkObj.get("href").getAsString(); + // do the fetch + logger.info("Loading: " + builder.toString()); + String webfingerResponse = restTemplate.getForObject(builder.build(), String.class); + + JsonElement json = parser.parse(webfingerResponse); + + if (json != null && json.isJsonObject()) { + // find the issuer + JsonArray links = json.getAsJsonObject().get("links").getAsJsonArray(); + for (JsonElement link : links) { + if (link.isJsonObject()) { + JsonObject linkObj = link.getAsJsonObject(); + if (linkObj.has("href") + && linkObj.has("rel") + && linkObj.get("rel").getAsString().equals("http://openid.net/specs/connect/1.0/issuer")) { + + // we found the issuer, return it + String href = linkObj.get("href").getAsString(); + + if (identifier.equals(href) + || identifier.startsWith("http")) { + // try to avoid sending a URL as the login hint + return new LoadingResult(null, href); + } else { + // otherwise pass back whatever the user typed as a login hint + return new LoadingResult(identifier, href); + } + } } } } + } catch (JsonParseException | RestClientException e) { + logger.warn("Failure in fetching webfinger input", e.getMessage()); } - // we couldn't find it + // we couldn't find it! if (key.getScheme().equals("http") || key.getScheme().equals("https")) { - // if it looks like HTTP then punt and return the input - logger.warn("Returning normalized input string as issuer, hoping for the best: " + key.toString()); - return key.toString(); + // if it looks like HTTP then punt: return the input, hope for the best + logger.warn("Returning normalized input string as issuer, hoping for the best: " + identifier); + return new LoadingResult(null, identifier); } else { // if it's not HTTP, give up - logger.warn("Couldn't find issuer: " + key.toString()); - return null; + logger.warn("Couldn't find issuer: " + identifier); + throw new IllegalArgumentException(); } } diff --git a/openid-connect-client/src/test/java/org/mitre/oauth2/introspectingfilter/TestOAuth2AccessTokenImpl.java b/openid-connect-client/src/test/java/org/mitre/oauth2/introspectingfilter/TestOAuth2AccessTokenImpl.java new file mode 100644 index 0000000000..051b5a26c2 --- /dev/null +++ b/openid-connect-client/src/test/java/org/mitre/oauth2/introspectingfilter/TestOAuth2AccessTokenImpl.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.oauth2.introspectingfilter; + +import java.util.Collections; +import java.util.Date; +import java.util.Set; + +import org.junit.Test; + +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonObject; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; + +import static org.junit.Assert.assertThat; + +public class TestOAuth2AccessTokenImpl { + + private static String tokenString = "thisisatokenstring"; + + private static Set scopes = ImmutableSet.of("bar", "foo"); + private static String scopeString = "foo bar"; + + private static Date exp = new Date(123 * 1000L); + private static Long expVal = 123L; + + @Test + public void testFullToken() { + + + JsonObject tokenObj = new JsonObject(); + tokenObj.addProperty("active", true); + tokenObj.addProperty("scope", scopeString); + tokenObj.addProperty("exp", expVal); + tokenObj.addProperty("sub", "subject"); + tokenObj.addProperty("client_id", "123-456-789"); + + OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString); + + assertThat(tok.getScope(), is(equalTo(scopes))); + assertThat(tok.getExpiration(), is(equalTo(exp))); + } + + @Test + public void testNullExp() { + + + JsonObject tokenObj = new JsonObject(); + tokenObj.addProperty("active", true); + tokenObj.addProperty("scope", scopeString); + tokenObj.addProperty("sub", "subject"); + tokenObj.addProperty("client_id", "123-456-789"); + + OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString); + + assertThat(tok.getScope(), is(equalTo(scopes))); + assertThat(tok.getExpiration(), is(equalTo(null))); + } + + @Test + public void testNullScopes() { + + + JsonObject tokenObj = new JsonObject(); + tokenObj.addProperty("active", true); + tokenObj.addProperty("exp", expVal); + tokenObj.addProperty("sub", "subject"); + tokenObj.addProperty("client_id", "123-456-789"); + + OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString); + + assertThat(tok.getScope(), is(equalTo(Collections.EMPTY_SET))); + assertThat(tok.getExpiration(), is(equalTo(exp))); + } + + @Test + public void testNullScopesNullExp() { + + + JsonObject tokenObj = new JsonObject(); + tokenObj.addProperty("active", true); + tokenObj.addProperty("sub", "subject"); + tokenObj.addProperty("client_id", "123-456-789"); + + OAuth2AccessTokenImpl tok = new OAuth2AccessTokenImpl(tokenObj, tokenString); + + assertThat(tok.getScope(), is(equalTo(Collections.EMPTY_SET))); + assertThat(tok.getExpiration(), is(equalTo(null))); + } + +} diff --git a/openid-connect-client/src/test/java/org/mitre/oauth2/introspectingfilter/service/impl/TestScopeBasedIntrospectionAuthoritiesGranter.java b/openid-connect-client/src/test/java/org/mitre/oauth2/introspectingfilter/service/impl/TestScopeBasedIntrospectionAuthoritiesGranter.java new file mode 100644 index 0000000000..2323019eff --- /dev/null +++ b/openid-connect-client/src/test/java/org/mitre/oauth2/introspectingfilter/service/impl/TestScopeBasedIntrospectionAuthoritiesGranter.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.introspectingfilter.service.impl; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import com.google.gson.JsonObject; + +import static org.junit.Assert.assertTrue; + +/** + * @author jricher + * + */ +public class TestScopeBasedIntrospectionAuthoritiesGranter { + + private JsonObject introspectionResponse; + + private ScopeBasedIntrospectionAuthoritiesGranter granter = new ScopeBasedIntrospectionAuthoritiesGranter(); + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + introspectionResponse = new JsonObject(); + } + + /** + * Test method for {@link org.mitre.oauth2.introspectingfilter.service.impl.ScopeBasedIntrospectionAuthoritiesGranter#getAuthorities(com.google.gson.JsonObject)}. + */ + @Test + public void testGetAuthoritiesJsonObject_withScopes() { + introspectionResponse.addProperty("scope", "foo bar baz batman"); + + List expected = new ArrayList<>(); + expected.add(new SimpleGrantedAuthority("ROLE_API")); + expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_foo")); + expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_bar")); + expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_baz")); + expected.add(new SimpleGrantedAuthority("OAUTH_SCOPE_batman")); + + List authorities = granter.getAuthorities(introspectionResponse); + + assertTrue(authorities.containsAll(expected)); + assertTrue(expected.containsAll(authorities)); + } + + /** + * Test method for {@link org.mitre.oauth2.introspectingfilter.service.impl.ScopeBasedIntrospectionAuthoritiesGranter#getAuthorities(com.google.gson.JsonObject)}. + */ + @Test + public void testGetAuthoritiesJsonObject_withoutScopes() { + + List expected = new ArrayList<>(); + expected.add(new SimpleGrantedAuthority("ROLE_API")); + + List authorities = granter.getAuthorities(introspectionResponse); + + assertTrue(authorities.containsAll(expected)); + assertTrue(expected.containsAll(authorities)); + } + +} diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/TestOIDCAuthenticationFilter.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/TestOIDCAuthenticationFilter.java new file mode 100644 index 0000000000..ae3018bbc8 --- /dev/null +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/TestOIDCAuthenticationFilter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.openid.connect.client; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.security.authentication.AuthenticationServiceException; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; + +import static org.mockito.Mockito.mock; + +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class TestOIDCAuthenticationFilter { + + private OIDCAuthenticationFilter filter = new OIDCAuthenticationFilter(); + + @Test + public void attemptAuthentication_error() throws Exception { + + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getParameter("error")).thenReturn("Error"); + Mockito.when(request.getParameter("error_description")).thenReturn("Description"); + Mockito.when(request.getParameter("error_uri")).thenReturn("http://example.com"); + + try { + filter.attemptAuthentication(request, mock(HttpServletResponse.class)); + + fail("AuthorizationEndpointException expected."); + } + catch (AuthorizationEndpointException exception) { + assertThat(exception.getMessage(), + is("Error from Authorization Endpoint: Error Description http://example.com")); + + assertThat(exception.getError(), is("Error")); + assertThat(exception.getErrorDescription(), is("Description")); + assertThat(exception.getErrorURI(), is("http://example.com")); + + assertThat(exception, is(instanceOf(AuthenticationServiceException.class))); + } + } +} diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridClientConfigurationService.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridClientConfigurationService.java index 373fb420b5..f7455981d9 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridClientConfigurationService.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridClientConfigurationService.java @@ -1,26 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +28,12 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + /** * @author wkim * diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridServerConfigurationService.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridServerConfigurationService.java index 07827c2672..c14e756f14 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridServerConfigurationService.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestHybridServerConfigurationService.java @@ -1,27 +1,23 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -32,6 +28,12 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + /** * @author wkim * diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestPlainAuthRequestUrlBuilder.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestPlainAuthRequestUrlBuilder.java index 6f7a735080..391afb612c 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestPlainAuthRequestUrlBuilder.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestPlainAuthRequestUrlBuilder.java @@ -1,24 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; - import java.util.Map; import org.junit.Before; @@ -31,6 +29,10 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; +import static org.hamcrest.CoreMatchers.equalTo; + +import static org.junit.Assert.assertThat; + /** * @author wkim * @@ -68,7 +70,27 @@ public void buildAuthRequestUrl() { Map options = ImmutableMap.of("foo", "bar"); - String actualUrl = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "https://client.example.org/", "34fasf3ds", "af0ifjsldkj", options); + String actualUrl = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "https://client.example.org/", "34fasf3ds", "af0ifjsldkj", options, null); + + assertThat(actualUrl, equalTo(expectedUrl)); + } + + @Test + public void buildAuthRequestUrl_withLoginHint() { + + String expectedUrl = "https://server.example.com/authorize?" + + "response_type=code" + + "&client_id=s6BhdRkqt3" + + "&scope=openid+profile" + // plus sign used for space per application/x-www-form-encoded standard + "&redirect_uri=https%3A%2F%2Fclient.example.org%2F" + + "&nonce=34fasf3ds" + + "&state=af0ifjsldkj" + + "&foo=bar" + + "&login_hint=bob"; + + Map options = ImmutableMap.of("foo", "bar"); + + String actualUrl = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "https://client.example.org/", "34fasf3ds", "af0ifjsldkj", options, "bob"); assertThat(actualUrl, equalTo(expectedUrl)); } @@ -80,7 +102,7 @@ public void buildAuthRequestUrl_badUri() { Map options = ImmutableMap.of("foo", "bar"); - urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "example.com", "", "", options); + urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "example.com", "", "", options, null); } } diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java index 0bbb83a823..1e733cadbf 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestSignedAuthRequestUrlBuilder.java @@ -1,25 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.net.URI; import java.net.URISyntaxException; import java.security.NoSuchAlgorithmException; @@ -31,7 +28,7 @@ import org.junit.Before; import org.junit.Test; -import org.mitre.jwt.signer.service.impl.DefaultJwtSigningAndValidationService; +import org.mitre.jwt.signer.service.impl.DefaultJWTSigningAndValidationService; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.config.ServerConfiguration; import org.mockito.Mockito; @@ -44,15 +41,19 @@ import com.google.common.collect.Sets; import com.nimbusds.jose.Algorithm; import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.Use; import com.nimbusds.jose.util.Base64URL; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * @author wkim - * + * */ public class TestSignedAuthRequestUrlBuilder { @@ -82,19 +83,20 @@ public class TestSignedAuthRequestUrlBuilder { "9Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q"; private String alg = "RS256"; private String kid = "2011-04-29"; + private String loginHint = "bob"; - private DefaultJwtSigningAndValidationService signingAndValidationService; + private DefaultJWTSigningAndValidationService signingAndValidationService; private SignedAuthRequestUrlBuilder urlBuilder = new SignedAuthRequestUrlBuilder(); @Before public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { - RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), Use.SIGNATURE, new Algorithm(alg), kid, null, null, null); + RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, new Algorithm(alg), kid, null, null, null, null, null); Map keys = Maps.newHashMap(); keys.put("client", key); - signingAndValidationService = new DefaultJwtSigningAndValidationService(keys); + signingAndValidationService = new DefaultJWTSigningAndValidationService(keys); signingAndValidationService.setDefaultSignerKeyId("client"); signingAndValidationService.setDefaultSigningAlgorithmName(alg); @@ -116,7 +118,46 @@ public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { @Test public void buildAuthRequestUrl() { - String requestUri = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options); + String requestUri = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, null); + + // parsing the result + UriComponentsBuilder builder = null; + + try { + builder = UriComponentsBuilder.fromUri(new URI(requestUri)); + } catch (URISyntaxException e1) { + fail("URISyntaxException was thrown."); + } + + UriComponents components = builder.build(); + String jwtString = components.getQueryParams().get("request").get(0); + JWTClaimsSet claims = null; + + try { + SignedJWT jwt = SignedJWT.parse(jwtString); + claims = jwt.getJWTClaimsSet(); + } catch (ParseException e) { + fail("ParseException was thrown."); + } + + assertEquals(responseType, claims.getClaim("response_type")); + assertEquals(clientConfig.getClientId(), claims.getClaim("client_id")); + + List scopeList = Arrays.asList(((String) claims.getClaim("scope")).split(" ")); + assertTrue(scopeList.containsAll(clientConfig.getScope())); + + assertEquals(redirectUri, claims.getClaim("redirect_uri")); + assertEquals(nonce, claims.getClaim("nonce")); + assertEquals(state, claims.getClaim("state")); + for (String claim : options.keySet()) { + assertEquals(options.get(claim), claims.getClaim(claim)); + } + } + + @Test + public void buildAuthRequestUrl_withLoginHint() { + + String requestUri = urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, redirectUri, nonce, state, options, loginHint); // parsing the result UriComponentsBuilder builder = null; @@ -129,7 +170,7 @@ public void buildAuthRequestUrl() { UriComponents components = builder.build(); String jwtString = components.getQueryParams().get("request").get(0); - ReadOnlyJWTClaimsSet claims = null; + JWTClaimsSet claims = null; try { SignedJWT jwt = SignedJWT.parse(jwtString); @@ -150,6 +191,7 @@ public void buildAuthRequestUrl() { for (String claim : options.keySet()) { assertEquals(options.get(claim), claims.getClaim(claim)); } + assertEquals(loginHint, claims.getClaim("login_hint")); } @Test(expected = AuthenticationServiceException.class) @@ -157,6 +199,6 @@ public void buildAuthRequestUrl_badUri() { Mockito.when(serverConfig.getAuthorizationEndpointUri()).thenReturn("e=mc^2"); - urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "example.com", "", "", options); + urlBuilder.buildAuthRequestUrl(serverConfig, clientConfig, "example.com", "", "", options, null); } } diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticClientConfigurationService.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticClientConfigurationService.java index 1c921bc5d5..4f251a4e3c 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticClientConfigurationService.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticClientConfigurationService.java @@ -1,27 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - import java.util.HashMap; import java.util.Map; @@ -34,6 +29,13 @@ import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + /** * @author wkim * @@ -56,7 +58,7 @@ public void prepare() { service = new StaticClientConfigurationService(); - Map clients = new HashMap(); + Map clients = new HashMap<>(); clients.put(issuer, mockClient); service.setClients(clients); diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticServerConfigurationService.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticServerConfigurationService.java index 6e819949a4..9f86bd3469 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticServerConfigurationService.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestStaticServerConfigurationService.java @@ -1,27 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - import java.util.HashMap; import java.util.Map; @@ -32,6 +27,13 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + /** * @author wkim * @@ -52,7 +54,7 @@ public void prepare() { service = new StaticServerConfigurationService(); - Map servers = new HashMap(); + Map servers = new HashMap<>(); servers.put(issuer, mockServerConfig); service.setServers(servers); diff --git a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestThirdPartyIssuerService.java b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestThirdPartyIssuerService.java index f32a07fd46..7a54e7d16c 100644 --- a/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestThirdPartyIssuerService.java +++ b/openid-connect-client/src/test/java/org/mitre/openid/connect/client/service/impl/TestThirdPartyIssuerService.java @@ -1,25 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.client.service.impl; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; - import javax.servlet.http.HttpServletRequest; import org.junit.Before; @@ -30,6 +27,11 @@ import com.google.common.collect.Sets; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; + +import static org.junit.Assert.assertThat; + /** * @author wkim * diff --git a/openid-connect-client/src/test/resources/test-context.xml b/openid-connect-client/src/test/resources/test-context.xml index d96dd5d9bf..51d33c9227 100644 --- a/openid-connect-client/src/test/resources/test-context.xml +++ b/openid-connect-client/src/test/resources/test-context.xml @@ -1,20 +1,21 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> - 4.0.0 - - openid-connect-parent - org.mitre - 1.1.2-SNAPSHOT - .. - - openid-connect-common - OpenID Connect Common modules - OpenID Connect Common - - - org.springframework - spring-core - ${org.springframework-version} - - - org.springframework - spring-webmvc - ${org.springframework-version} - - - org.springframework.security - spring-security-core - ${spring.security.version} - - - org.springframework - * - - - - - com.google.guava - guava - 16.0 - - - org.apache.httpcomponents - httpclient - 4.2.3 - - - org.springframework.security.oauth - 2.0.0.M2 - spring-security-oauth2 - - - com.nimbusds - nimbus-jose-jwt - 2.22.1 - - - org.eclipse.persistence - javax.persistence - 2.0.3 - - - com.google.code.gson - gson - 2.0 - - - - - org.slf4j - slf4j-api - ${org.slf4j-version} - - + 4.0.0 + + openid-connect-parent + org.mitre + 1.3.5-SNAPSHOT + .. + + openid-connect-common + OpenID Connect Common modules + OpenID Connect Common + + + org.springframework + spring-core + + + commons-logging + commons-logging + + + + + org.springframework + spring-webmvc + + + org.springframework.security + spring-security-core + + + com.google.guava + guava + + + org.apache.httpcomponents + httpclient + + + org.springframework.security.oauth + spring-security-oauth2 + + + com.nimbusds + nimbus-jose-jwt + + + org.eclipse.persistence + javax.persistence + + + com.google.code.gson + gson + + + org.slf4j + slf4j-api + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + org.bouncycastle + bcprov-jdk15on + + + javax.annotation + javax.annotation-api + + + jakarta.xml.bind + jakarta.xml.bind-api + + + javax.xml.bind + jaxb-api + + + javax.activation + activation + + + org.glassfish.jaxb + jaxb-runtime + + + + jar - - jar - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${java-version} - ${java-version} - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar-no-fork - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-sources - - jar - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java-version} + ${java-version} + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-sources + + jar + + + + + + diff --git a/openid-connect-common/src/main/java/org/mitre/data/AbstractPageOperationTemplate.java b/openid-connect-common/src/main/java/org/mitre/data/AbstractPageOperationTemplate.java new file mode 100644 index 0000000000..1962366f8c --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/data/AbstractPageOperationTemplate.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.data; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for performing an operation on a potentially large + * number of items by paging through the items in discreet chunks. + * + * @param the type parameter + * @author Colm Smyth. + */ +public abstract class AbstractPageOperationTemplate { + + private static final Logger logger = LoggerFactory.getLogger(AbstractPageOperationTemplate.class); + + private static int DEFAULT_MAX_PAGES = 1000; + private static long DEFAULT_MAX_TIME_MILLIS = 600000L; //10 Minutes + + /** + * int specifying the maximum number of + * pages which should be fetched before + * execution should terminate + */ + private int maxPages; + + /** + * long specifying the maximum execution time + * in milliseconds + */ + private long maxTime; + + /** + * boolean specifying whether or not Exceptions + * incurred performing the operation should be + * swallowed during execution default true. + */ + private boolean swallowExceptions = true; + + /** + * String that is used for logging in final tallies. + */ + private String operationName = ""; + + + /** + * default constructor which sets the value of + * maxPages and maxTime to DEFAULT_MAX_PAGES and + * DEFAULT_MAX_TIME_MILLIS respectively + */ + public AbstractPageOperationTemplate(String operationName){ + this(DEFAULT_MAX_PAGES, DEFAULT_MAX_TIME_MILLIS, operationName); + } + + /** + * Instantiates a new AbstractPageOperationTemplate with the + * given maxPages and maxTime + * + * @param maxPages the maximum number of pages to fetch. + * @param maxTime the maximum execution time. + */ + public AbstractPageOperationTemplate(int maxPages, long maxTime, String operationName){ + this.maxPages = maxPages; + this.maxTime = maxTime; + this.operationName = operationName; + } + + /** + * Execute the operation on each member of a page of results + * retrieved through the fetch method. the method will execute + * until either the maxPages or maxTime limit is reached or until + * the fetch method returns no more results. Exceptions thrown + * performing the operation on the item will be swallowed if the + * swallowException (default true) field is set true. + */ + public void execute(){ + logger.debug("[" + getOperationName() + "] Starting execution of paged operation. maximum time: " + maxTime + ", maximum pages: " + maxPages); + + long startTime = System.currentTimeMillis(); + long executionTime = 0; + int i = 0; + + int exceptionsSwallowedCount = 0; + int operationsCompleted = 0; + Set exceptionsSwallowedClasses = new HashSet(); + + + while (i< maxPages && executionTime < maxTime){ + Collection page = fetchPage(); + if(page == null || page.size() == 0){ + break; + } + + for (T item : page) { + try { + doOperation(item); + operationsCompleted++; + } catch (Exception e){ + if(swallowExceptions){ + exceptionsSwallowedCount++; + exceptionsSwallowedClasses.add(e.getClass().getName()); + logger.debug("Swallowing exception " + e.getMessage(), e); + } else { + logger.debug("Rethrowing exception " + e.getMessage()); + throw e; + } + } + } + + i++; + executionTime = System.currentTimeMillis() - startTime; + } + + finalReport(operationsCompleted, exceptionsSwallowedCount, exceptionsSwallowedClasses); + } + + + + /** + * method responsible for fetching + * a page of items. + * + * @return the collection of items + */ + public abstract Collection fetchPage(); + + /** + * method responsible for performing desired + * operation on a fetched page item. + * + * @param item the item + */ + protected abstract void doOperation(T item); + + /** + * Method responsible for final report of progress. + * @return + */ + protected void finalReport(int operationsCompleted, int exceptionsSwallowedCount, Set exceptionsSwallowedClasses) { + if (operationsCompleted > 0 || exceptionsSwallowedCount > 0) { + logger.info("[" + getOperationName() + "] Paged operation run: completed " + operationsCompleted + "; swallowed " + exceptionsSwallowedCount + " exceptions"); + } + for(String className: exceptionsSwallowedClasses) { + logger.warn("[" + getOperationName() + "] Paged operation swallowed at least one exception of type " + className); + } + } + + public int getMaxPages() { + return maxPages; + } + + public void setMaxPages(int maxPages) { + this.maxPages = maxPages; + } + + public long getMaxTime() { + return maxTime; + } + + public void setMaxTime(long maxTime) { + this.maxTime = maxTime; + } + + public boolean isSwallowExceptions() { + return swallowExceptions; + } + + public void setSwallowExceptions(boolean swallowExceptions) { + this.swallowExceptions = swallowExceptions; + } + + + /** + * @return the operationName + */ + public String getOperationName() { + return operationName; + } + + + /** + * @param operationName the operationName to set + */ + public void setOperationName(String operationName) { + this.operationName = operationName; + } +} diff --git a/openid-connect-common/src/main/java/org/mitre/data/DefaultPageCriteria.java b/openid-connect-common/src/main/java/org/mitre/data/DefaultPageCriteria.java new file mode 100644 index 0000000000..daec512b0a --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/data/DefaultPageCriteria.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.data; + +/** + * Default implementation of PageCriteria which specifies + * both page to be retrieved and page size in the constructor. + * + * @author Colm Smyth + */ +public class DefaultPageCriteria implements PageCriteria { + + private static final int DEFAULT_PAGE_NUMBER = 0; + private static final int DEFAULT_PAGE_SIZE = 100; + + private int pageNumber; + private int pageSize; + + public DefaultPageCriteria(){ + this(DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE); + } + + public DefaultPageCriteria(int pageNumber, int pageSize) { + this.pageNumber = pageNumber; + this.pageSize = pageSize; + } + + @Override + public int getPageNumber() { + return pageNumber; + } + + @Override + public int getPageSize() { + return pageSize; + } +} diff --git a/openid-connect-common/src/main/java/org/mitre/data/PageCriteria.java b/openid-connect-common/src/main/java/org/mitre/data/PageCriteria.java new file mode 100644 index 0000000000..2db1077d94 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/data/PageCriteria.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.data; + +/** + * Interface which defines page criteria for use in + * a repository operation. + * + * @author Colm Smyth + */ +public interface PageCriteria { + + public int getPageNumber(); + public int getPageSize(); +} diff --git a/openid-connect-common/src/main/java/org/mitre/discovery/util/JsonUtils.java b/openid-connect-common/src/main/java/org/mitre/discovery/util/JsonUtils.java deleted file mode 100644 index 562a2ef35f..0000000000 --- a/openid-connect-common/src/main/java/org/mitre/discovery/util/JsonUtils.java +++ /dev/null @@ -1,193 +0,0 @@ -/** - * - */ -package org.mitre.discovery.util; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Set; - -import com.google.gson.Gson; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; -import com.google.gson.reflect.TypeToken; -import com.nimbusds.jose.EncryptionMethod; -import com.nimbusds.jose.JWEAlgorithm; -import com.nimbusds.jose.JWSAlgorithm; - -/** - * A collection of null-safe converters from common classes and JSON elements, using GSON. - * - * @author jricher - * - */ -public class JsonUtils { - - private static Gson gson = new Gson(); - - /** - * Translate a set of strings to a JSON array - * @param value - * @return - */ - public static JsonElement getAsArray(Set value) { - return gson.toJsonTree(value, new TypeToken>(){}.getType()); - } - - /** - * Gets the value of the given member (expressed as integer seconds since epoch) as a Date - */ - public static Date getAsDate(JsonObject o, String member) { - if (o.has(member)) { - JsonElement e = o.get(member); - if (e != null && e.isJsonPrimitive()) { - return new Date(e.getAsInt() * 1000L); - } else { - return null; - } - } else { - return null; - } - } - - /** - * Gets the value of the given member as a JWE Algorithm, null if it doesn't exist - */ - public static JWEAlgorithm getAsJweAlgorithm(JsonObject o, String member) { - String s = getAsString(o, member); - if (s != null) { - return JWEAlgorithm.parse(s); - } else { - return null; - } - } - - /** - * Gets the value of the given member as a JWE Encryption Method, null if it doesn't exist - */ - public static EncryptionMethod getAsJweEncryptionMethod(JsonObject o, String member) { - String s = getAsString(o, member); - if (s != null) { - return EncryptionMethod.parse(s); - } else { - return null; - } - } - - /** - * Gets the value of the given member as a JWS Algorithm, null if it doesn't exist - */ - public static JWSAlgorithm getAsJwsAlgorithm(JsonObject o, String member) { - String s = getAsString(o, member); - if (s != null) { - return JWSAlgorithm.parse(s); - } else { - return null; - } - } - - /** - * Gets the value of the given member as a string, null if it doesn't exist - */ - public static String getAsString(JsonObject o, String member) { - if (o.has(member)) { - JsonElement e = o.get(member); - if (e != null && e.isJsonPrimitive()) { - return e.getAsString(); - } else { - return null; - } - } else { - return null; - } - } - - /** - * Gets the value of the given member as a boolean, null if it doesn't exist - */ - public static Boolean getAsBoolean(JsonObject o, String member) { - if (o.has(member)) { - JsonElement e = o.get(member); - if (e != null && e.isJsonPrimitive()) { - return e.getAsBoolean(); - } else { - return null; - } - } else { - return null; - } - } - - /** - * Gets the value of the given given member as a set of strings, null if it doesn't exist - */ - public static Set getAsStringSet(JsonObject o, String member) throws JsonSyntaxException { - if (o.has(member)) { - return gson.fromJson(o.get(member), new TypeToken>(){}.getType()); - } else { - return null; - } - } - - /** - * Gets the value of the given given member as a set of strings, null if it doesn't exist - */ - public static List getAsStringList(JsonObject o, String member) throws JsonSyntaxException { - if (o.has(member)) { - return gson.fromJson(o.get(member), new TypeToken>(){}.getType()); - } else { - return null; - } - } - - /** - * Gets the value of the given member as a list of JWS Algorithms, null if it doesn't exist - */ - public static List getAsJwsAlgorithmList(JsonObject o, String member) { - List strings = getAsStringList(o, member); - if (strings != null) { - List algs = new ArrayList(); - for (String alg : strings) { - algs.add(JWSAlgorithm.parse(alg)); - } - return algs; - } else { - return null; - } - } - - /** - * Gets the value of the given member as a list of JWS Algorithms, null if it doesn't exist - */ - public static List getAsJweAlgorithmList(JsonObject o, String member) { - List strings = getAsStringList(o, member); - if (strings != null) { - List algs = new ArrayList(); - for (String alg : strings) { - algs.add(JWEAlgorithm.parse(alg)); - } - return algs; - } else { - return null; - } - } - - /** - * Gets the value of the given member as a list of JWS Algorithms, null if it doesn't exist - */ - public static List getAsEncryptionMethodList(JsonObject o, String member) { - List strings = getAsStringList(o, member); - if (strings != null) { - List algs = new ArrayList(); - for (String alg : strings) { - algs.add(EncryptionMethod.parse(alg)); - } - return algs; - } else { - return null; - } - } - -} diff --git a/openid-connect-common/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java b/openid-connect-common/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java index 84e63cf400..b71fbf6d6a 100644 --- a/openid-connect-common/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java +++ b/openid-connect-common/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.discovery.util; import java.util.regex.Matcher; @@ -29,13 +30,16 @@ /** * Provides utility methods for normalizing and parsing URIs for use with Webfinger Discovery. - * + * * @author wkim * */ public class WebfingerURLNormalizer { - private static Logger logger = LoggerFactory.getLogger(WebfingerURLNormalizer.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(WebfingerURLNormalizer.class); // pattern used to parse user input; we can't use the built-in java URI parser private static final Pattern pattern = Pattern.compile("^" + diff --git a/openid-connect-common/src/main/java/org/mitre/jose/JWEAlgorithmEmbed.java b/openid-connect-common/src/main/java/org/mitre/jose/JWEAlgorithmEmbed.java deleted file mode 100644 index 8e6fd44b37..0000000000 --- a/openid-connect-common/src/main/java/org/mitre/jose/JWEAlgorithmEmbed.java +++ /dev/null @@ -1,110 +0,0 @@ -/******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -/** - * - */ -package org.mitre.jose; - -import javax.persistence.Basic; -import javax.persistence.Embeddable; -import javax.persistence.Transient; - -import com.google.common.base.Strings; -import com.nimbusds.jose.JWEAlgorithm; - -/** - * - * Wrapper class for Nimbus JOSE objects to fit into JPA - * - * @author jricher - * - */ -@Embeddable -public class JWEAlgorithmEmbed { - - public static final JWEAlgorithmEmbed NONE = getForAlgorithmName("none"); - - private JWEAlgorithm algorithm; - - public JWEAlgorithmEmbed() { - - } - - public JWEAlgorithmEmbed(JWEAlgorithm algorithm) { - this.algorithm = algorithm; - } - - public static JWEAlgorithmEmbed getForAlgorithmName (String algorithmName) { - JWEAlgorithmEmbed ent = new JWEAlgorithmEmbed(); - ent.setAlgorithmName(algorithmName); - if (ent.getAlgorithm() == null) { - return null; - } else { - return ent; - } - } - - /** - * Get the name of this algorithm, return null if no algorithm set. - * @return - */ - @Basic - public String getAlgorithmName() { - if (algorithm != null) { - return algorithm.getName(); - } else { - return null; - } - } - - /** - * Set the name of this algorithm. - * Calls JWEAlgorithm.parse() - * @param algorithmName - */ - public void setAlgorithmName(String algorithmName) { - if (!Strings.isNullOrEmpty(algorithmName)) { - algorithm = JWEAlgorithm.parse(algorithmName); - } else { - algorithm = null; - } - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "JWEAlgorithmEmbed [algorithm=" + algorithm + "]"; - } - - /** - * @return the algorithm - */ - @Transient - public JWEAlgorithm getAlgorithm() { - return algorithm; - } - - /** - * @param algorithm the algorithm to set - */ - public void setAlgorithm(JWEAlgorithm algorithm) { - this.algorithm = algorithm; - } - -} diff --git a/openid-connect-common/src/main/java/org/mitre/jose/JWEEncryptionMethodEmbed.java b/openid-connect-common/src/main/java/org/mitre/jose/JWEEncryptionMethodEmbed.java deleted file mode 100644 index f70f7e1520..0000000000 --- a/openid-connect-common/src/main/java/org/mitre/jose/JWEEncryptionMethodEmbed.java +++ /dev/null @@ -1,108 +0,0 @@ -/******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -/** - * - */ -package org.mitre.jose; - -import javax.persistence.Basic; -import javax.persistence.Embeddable; -import javax.persistence.Transient; - -import com.google.common.base.Strings; -import com.nimbusds.jose.EncryptionMethod; - -/** - * @author jricher - * - */ -@Embeddable -public class JWEEncryptionMethodEmbed { - - public static final JWEEncryptionMethodEmbed NONE = getForAlgorithmName("none"); - - private EncryptionMethod algorithm; - - public JWEEncryptionMethodEmbed() { - - } - - public JWEEncryptionMethodEmbed(EncryptionMethod algorithm) { - this.algorithm = algorithm; - } - - public static JWEEncryptionMethodEmbed getForAlgorithmName (String algorithmName) { - JWEEncryptionMethodEmbed ent = new JWEEncryptionMethodEmbed(); - ent.setAlgorithmName(algorithmName); - if (ent.getAlgorithm() == null) { - return null; - } else { - return ent; - } - } - - /** - * Get the name of this algorithm, return null if no algorithm set. - * @return - */ - @Basic - public String getAlgorithmName() { - if (algorithm != null) { - return algorithm.getName(); - } else { - return null; - } - } - - /** - * Set the name of this algorithm. - * Calls EncryptionMethod.parse() - * @param algorithmName - */ - public void setAlgorithmName(String algorithmName) { - if (!Strings.isNullOrEmpty(algorithmName)) { - algorithm = EncryptionMethod.parse(algorithmName); - } else { - algorithm = null; - } - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "JWEEncryptionMethodEmbed [algorithm=" + algorithm + "]"; - } - - /** - * @return the algorithm - */ - @Transient - public EncryptionMethod getAlgorithm() { - return algorithm; - } - - /** - * @param algorithm the algorithm to set - */ - public void setAlgorithm(EncryptionMethod algorithm) { - this.algorithm = algorithm; - } - - -} diff --git a/openid-connect-common/src/main/java/org/mitre/jose/JWSAlgorithmEmbed.java b/openid-connect-common/src/main/java/org/mitre/jose/JWSAlgorithmEmbed.java deleted file mode 100644 index 3c67f57fb7..0000000000 --- a/openid-connect-common/src/main/java/org/mitre/jose/JWSAlgorithmEmbed.java +++ /dev/null @@ -1,112 +0,0 @@ -/******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -/** - * - */ -package org.mitre.jose; - -import javax.persistence.Basic; -import javax.persistence.Embeddable; -import javax.persistence.Transient; - -import com.google.common.base.Strings; -import com.nimbusds.jose.JWSAlgorithm; - -/** - * - * Wrapper class for Nimbus JOSE objects to fit into JPA - * - * @author jricher - * - */ -@Embeddable -public class JWSAlgorithmEmbed { - - public static final JWSAlgorithmEmbed NONE = getForAlgorithmName("none"); - - private JWSAlgorithm algorithm; - - public JWSAlgorithmEmbed() { - - } - - public JWSAlgorithmEmbed(JWSAlgorithm algorithm) { - this.algorithm = algorithm; - } - - public static JWSAlgorithmEmbed getForAlgorithmName (String algorithmName) { - JWSAlgorithmEmbed ent = new JWSAlgorithmEmbed(); - ent.setAlgorithmName(algorithmName); - if (ent.getAlgorithm() == null) { - return null; - } else { - return ent; - } - } - - /** - * Get the name of this algorithm, return null if no algorithm set. - * @return - */ - @Basic - public String getAlgorithmName() { - if (algorithm != null) { - return algorithm.getName(); - } else { - return null; - } - } - - /** - * Set the name of this algorithm. - * Calls JWSAlgorithm.parse() - * @param algorithmName - */ - public void setAlgorithmName(String algorithmName) { - if (!Strings.isNullOrEmpty(algorithmName)) { - algorithm = JWSAlgorithm.parse(algorithmName); - } else { - algorithm = null; - } - } - - /** - * @return the algorithm - */ - @Transient - public JWSAlgorithm getAlgorithm() { - return algorithm; - } - - /** - * @param algorithm the algorithm to set - */ - public void setAlgorithm(JWSAlgorithm algorithm) { - this.algorithm = algorithm; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "JWSAlgorithmEmbed [algorithm=" + algorithm + "]"; - } - - - -} diff --git a/openid-connect-common/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java b/openid-connect-common/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java index 06c36c3c76..f40997b8dc 100644 --- a/openid-connect-common/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java +++ b/openid-connect-common/src/main/java/org/mitre/jose/keystore/JWKSetKeyStore.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.jose.keystore; diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/assertion/AssertionValidator.java b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/AssertionValidator.java new file mode 100644 index 0000000000..ffc29a2e94 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/AssertionValidator.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.jwt.assertion; + +import com.nimbusds.jwt.JWT; + +/** + * @author jricher + * + */ +public interface AssertionValidator { + + public boolean isValid(JWT assertion); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/NullAssertionValidator.java b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/NullAssertionValidator.java new file mode 100644 index 0000000000..5fa53e6c85 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/NullAssertionValidator.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.jwt.assertion.impl; + +import org.mitre.jwt.assertion.AssertionValidator; + +import com.nimbusds.jwt.JWT; + +/** + * Reject all assertions passed in. + * + * @author jricher + * + */ +public class NullAssertionValidator implements AssertionValidator { + + /** + * Reject all assertions passed in, always returns false. + */ + @Override + public boolean isValid(JWT assertion) { + return false; + + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java new file mode 100644 index 0000000000..5655c93c73 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.jwt.assertion.impl; + +import java.text.ParseException; + +import org.mitre.jwt.assertion.AssertionValidator; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.common.base.Strings; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +/** + * Validates all assertions generated by this server + * + * @author jricher + * + */ +@Component("selfAssertionValidator") +public class SelfAssertionValidator implements AssertionValidator { + + private static Logger logger = LoggerFactory.getLogger(SelfAssertionValidator.class); + + @Autowired + private ConfigurationPropertiesBean config; + + @Autowired + private JWTSigningAndValidationService jwtService; + + @Override + public boolean isValid(JWT assertion) { + if (!(assertion instanceof SignedJWT)) { + // unsigned assertion + return false; + } + + JWTClaimsSet claims; + try { + claims = assertion.getJWTClaimsSet(); + } catch (ParseException e) { + logger.debug("Invalid assertion claims"); + return false; + } + + // make sure the issuer exists + if (Strings.isNullOrEmpty(claims.getIssuer())) { + logger.debug("No issuer for assertion, rejecting"); + return false; + } + + // make sure the issuer is us + if (!claims.getIssuer().equals(config.getIssuer())) { + logger.debug("Issuer is not the same as this server, rejecting"); + return false; + } + + // validate the signature based on our public key + if (jwtService.validateSignature((SignedJWT) assertion)) { + return true; + } else { + return false; + } + + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java new file mode 100644 index 0000000000..87f5986cea --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/WhitelistedIssuerAssertionValidator.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.jwt.assertion.impl; + +import java.text.ParseException; +import java.util.HashMap; +import java.util.Map; + +import org.mitre.jwt.assertion.AssertionValidator; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.jwt.signer.service.impl.JWKSetCacheService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.base.Strings; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +/** + * Checks to see if the assertion was signed by a particular authority available from a whitelist + * @author jricher + * + */ +public class WhitelistedIssuerAssertionValidator implements AssertionValidator { + + private static Logger logger = LoggerFactory.getLogger(WhitelistedIssuerAssertionValidator.class); + + /** + * Map of issuer -> JWKSetUri + */ + private Map whitelist = new HashMap<>(); + + /** + * @return the whitelist + */ + public Map getWhitelist() { + return whitelist; + } + + /** + * @param whitelist the whitelist to set + */ + public void setWhitelist(Map whitelist) { + this.whitelist = whitelist; + } + + @Autowired + private JWKSetCacheService jwkCache; + + @Override + public boolean isValid(JWT assertion) { + + if (!(assertion instanceof SignedJWT)) { + // unsigned assertion + return false; + } + + JWTClaimsSet claims; + try { + claims = assertion.getJWTClaimsSet(); + } catch (ParseException e) { + logger.debug("Invalid assertion claims"); + return false; + } + + if (Strings.isNullOrEmpty(claims.getIssuer())) { + logger.debug("No issuer for assertion, rejecting"); + return false; + } + + if (!whitelist.containsKey(claims.getIssuer())) { + logger.debug("Issuer is not in whitelist, rejecting"); + return false; + } + + String jwksUri = whitelist.get(claims.getIssuer()); + + JWTSigningAndValidationService validator = jwkCache.getValidator(jwksUri); + + if (validator.validateSignature((SignedJWT) assertion)) { + return true; + } else { + return false; + } + + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/JwtEncryptionAndDecryptionService.java b/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/JWTEncryptionAndDecryptionService.java similarity index 91% rename from openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/JwtEncryptionAndDecryptionService.java rename to openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/JWTEncryptionAndDecryptionService.java index 447c58bc5a..0489af58d9 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/JwtEncryptionAndDecryptionService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/JWTEncryptionAndDecryptionService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.jwt.encryption.service; import java.util.Collection; @@ -28,7 +29,7 @@ * @author wkim * */ -public interface JwtEncryptionAndDecryptionService { +public interface JWTEncryptionAndDecryptionService { /** * Encrypts the JWT in place with the default encrypter. diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJwtEncryptionAndDecryptionService.java b/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java similarity index 70% rename from openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJwtEncryptionAndDecryptionService.java rename to openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java index bc9e53822d..dbe8a530bb 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJwtEncryptionAndDecryptionService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/encryption/service/impl/DefaultJWTEncryptionAndDecryptionService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.jwt.encryption.service.impl; import java.security.NoSuchAlgorithmException; @@ -27,7 +28,7 @@ import javax.annotation.PostConstruct; import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService; +import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,8 +41,12 @@ import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.crypto.DirectDecrypter; import com.nimbusds.jose.crypto.DirectEncrypter; +import com.nimbusds.jose.crypto.ECDHDecrypter; +import com.nimbusds.jose.crypto.ECDHEncrypter; import com.nimbusds.jose.crypto.RSADecrypter; import com.nimbusds.jose.crypto.RSAEncrypter; +import com.nimbusds.jose.crypto.bc.BouncyCastleProviderSingleton; +import com.nimbusds.jose.jwk.ECKey; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.OctetSequenceKey; import com.nimbusds.jose.jwk.RSAKey; @@ -50,15 +55,18 @@ * @author wkim * */ -public class DefaultJwtEncryptionAndDecryptionService implements JwtEncryptionAndDecryptionService { +public class DefaultJWTEncryptionAndDecryptionService implements JWTEncryptionAndDecryptionService { - private static Logger logger = LoggerFactory.getLogger(DefaultJwtEncryptionAndDecryptionService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(DefaultJWTEncryptionAndDecryptionService.class); // map of identifier to encrypter - private Map encrypters = new HashMap(); + private Map encrypters = new HashMap<>(); // map of identifier to decrypter - private Map decrypters = new HashMap(); + private Map decrypters = new HashMap<>(); private String defaultEncryptionKeyId; @@ -67,18 +75,18 @@ public class DefaultJwtEncryptionAndDecryptionService implements JwtEncryptionAn private JWEAlgorithm defaultAlgorithm; // map of identifier to key - private Map keys = new HashMap(); + private Map keys = new HashMap<>(); /** * Build this service based on the keys given. All public keys will be used to make encrypters, * all private keys will be used to make decrypters. - * + * * @param keys * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException * @throws JOSEException */ - public DefaultJwtEncryptionAndDecryptionService(Map keys) throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException { + public DefaultJWTEncryptionAndDecryptionService(Map keys) throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException { this.keys = keys; buildEncryptersAndDecrypters(); } @@ -86,13 +94,13 @@ public DefaultJwtEncryptionAndDecryptionService(Map keys) throws No /** * Build this service based on the given keystore. All keys must have a key * id ({@code kid}) field in order to be used. - * + * * @param keyStore * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException * @throws JOSEException */ - public DefaultJwtEncryptionAndDecryptionService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException { + public DefaultJWTEncryptionAndDecryptionService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException { // convert all keys in the keystore to a map based on key id for (JWK key : keyStore.getKeys()) { @@ -109,12 +117,20 @@ public DefaultJwtEncryptionAndDecryptionService(JWKSetKeyStore keyStore) throws @PostConstruct - public void afterPropertiesSet() throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException{ + public void afterPropertiesSet() { if (keys == null) { throw new IllegalArgumentException("Encryption and decryption service must have at least one key configured."); } - buildEncryptersAndDecrypters(); + try { + buildEncryptersAndDecrypters(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Encryption and decryption service could not find given algorithm."); + } catch (InvalidKeySpecException e) { + throw new IllegalArgumentException("Encryption and decryption service saw an invalid key specification."); + } catch (JOSEException e) { + throw new IllegalArgumentException("Encryption and decryption service was unable to process JOSE object."); + } } public String getDefaultEncryptionKeyId() { @@ -212,23 +228,40 @@ private void buildEncryptersAndDecrypters() throws NoSuchAlgorithmException, Inv if (jwk instanceof RSAKey) { // build RSA encrypters and decrypters - RSAEncrypter encrypter = new RSAEncrypter(((RSAKey) jwk).toRSAPublicKey()); // there should always at least be the public key + RSAEncrypter encrypter = new RSAEncrypter((RSAKey) jwk); // there should always at least be the public key + encrypter.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance()); encrypters.put(id, encrypter); if (jwk.isPrivate()) { // we can decrypt! - RSADecrypter decrypter = new RSADecrypter(((RSAKey) jwk).toRSAPrivateKey()); + RSADecrypter decrypter = new RSADecrypter((RSAKey) jwk); + decrypter.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance()); decrypters.put(id, decrypter); } else { logger.warn("No private key for key #" + jwk.getKeyID()); } + } else if (jwk instanceof ECKey) { + + // build EC Encrypters and decrypters + + ECDHEncrypter encrypter = new ECDHEncrypter((ECKey) jwk); + encrypter.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance()); + encrypters.put(id, encrypter); - // TODO: add support for EC keys + if (jwk.isPrivate()) { // we can decrypt too + ECDHDecrypter decrypter = new ECDHDecrypter((ECKey) jwk); + decrypter.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance()); + decrypters.put(id, decrypter); + } else { + logger.warn("No private key for key # " + jwk.getKeyID()); + } } else if (jwk instanceof OctetSequenceKey) { // build symmetric encrypters and decrypters - DirectEncrypter encrypter = new DirectEncrypter(((OctetSequenceKey) jwk).toByteArray()); - DirectDecrypter decrypter = new DirectDecrypter(((OctetSequenceKey) jwk).toByteArray()); + DirectEncrypter encrypter = new DirectEncrypter((OctetSequenceKey) jwk); + encrypter.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance()); + DirectDecrypter decrypter = new DirectDecrypter((OctetSequenceKey) jwk); + decrypter.getJCAContext().setProvider(BouncyCastleProviderSingleton.getInstance()); encrypters.put(id, encrypter); decrypters.put(id, decrypter); @@ -242,7 +275,7 @@ private void buildEncryptersAndDecrypters() throws NoSuchAlgorithmException, Inv @Override public Map getAllPublicKeys() { - Map pubKeys = new HashMap(); + Map pubKeys = new HashMap<>(); // pull out all public keys for (String keyId : keys.keySet()) { @@ -258,14 +291,14 @@ public Map getAllPublicKeys() { @Override public Collection getAllEncryptionAlgsSupported() { - Set algs = new HashSet(); + Set algs = new HashSet<>(); for (JWEEncrypter encrypter : encrypters.values()) { - algs.addAll(encrypter.supportedAlgorithms()); + algs.addAll(encrypter.supportedJWEAlgorithms()); } for (JWEDecrypter decrypter : decrypters.values()) { - algs.addAll(decrypter.supportedAlgorithms()); + algs.addAll(decrypter.supportedJWEAlgorithms()); } return algs; @@ -276,7 +309,7 @@ public Collection getAllEncryptionAlgsSupported() { */ @Override public Collection getAllEncryptionEncsSupported() { - Set encs = new HashSet(); + Set encs = new HashSet<>(); for (JWEEncrypter encrypter : encrypters.values()) { encs.addAll(encrypter.supportedEncryptionMethods()); diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JWTSigningAndValidationService.java similarity index 91% rename from openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java rename to openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JWTSigningAndValidationService.java index 61d807b1cd..1bb05f1421 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JwtSigningAndValidationService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/JWTSigningAndValidationService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.jwt.signer.service; import java.security.NoSuchAlgorithmException; @@ -24,7 +25,7 @@ import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jwt.SignedJWT; -public interface JwtSigningAndValidationService { +public interface JWTSigningAndValidationService { /** * Get all public keys for this service, mapped by their Key ID @@ -34,7 +35,7 @@ public interface JwtSigningAndValidationService { /** * Checks the signature of the given JWT against all configured signers, * returns true if at least one of the signers validates it. - * + * * @param jwtString * the string representation of the JWT as sent on the wire * @return true if the signature is valid, false if not @@ -45,7 +46,7 @@ public interface JwtSigningAndValidationService { /** * Called to sign a jwt in place for a client that hasn't registered a preferred signing algorithm. * Use the default algorithm to sign. - * + * * @param jwt the jwt to sign * @return the signed jwt * @throws NoSuchAlgorithmException @@ -67,13 +68,15 @@ public interface JwtSigningAndValidationService { /** * Sign a jwt using the selected algorithm. The algorithm is selected using the String parameter values specified * in the JWT spec, section 6. I.E., "HS256" means HMAC with SHA-256 and corresponds to our HmacSigner class. - * + * * @param jwt the jwt to sign * @param alg the name of the algorithm to use, as specified in JWS s.6 * @return the signed jwt */ public void signJwt(SignedJWT jwt, JWSAlgorithm alg); + public String getDefaultSignerKeyId(); + /** * TODO: method to sign a jwt using a specified algorithm and a key id */ diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/ClientKeyCacheService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/ClientKeyCacheService.java new file mode 100644 index 0000000000..3e46b9b4bd --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/ClientKeyCacheService.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.jwt.signer.service.impl; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.mitre.jose.keystore.JWKSetKeyStore; +import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; +import org.mitre.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; + +/** + * + * Takes in a client and returns the appropriate validator or encrypter for + * that client's registered key types. + * + * @author jricher + * + */ +@Service +public class ClientKeyCacheService { + + private static Logger logger = LoggerFactory.getLogger(ClientKeyCacheService.class); + + @Autowired + private JWKSetCacheService jwksUriCache = new JWKSetCacheService(); + + @Autowired + private SymmetricKeyJWTValidatorCacheService symmetricCache = new SymmetricKeyJWTValidatorCacheService(); + + // cache of validators for by-value JWKs + private LoadingCache jwksValidators; + + // cache of encryptors for by-value JWKs + private LoadingCache jwksEncrypters; + + public ClientKeyCacheService() { + this.jwksValidators = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch + .maximumSize(100) + .build(new JWKSetVerifierBuilder()); + this.jwksEncrypters = CacheBuilder.newBuilder() + .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch + .maximumSize(100) + .build(new JWKSetEncryptorBuilder()); + } + + + public JWTSigningAndValidationService getValidator(ClientDetailsEntity client, JWSAlgorithm alg) { + + try { + if (alg.equals(JWSAlgorithm.RS256) + || alg.equals(JWSAlgorithm.RS384) + || alg.equals(JWSAlgorithm.RS512) + || alg.equals(JWSAlgorithm.ES256) + || alg.equals(JWSAlgorithm.ES384) + || alg.equals(JWSAlgorithm.ES512) + || alg.equals(JWSAlgorithm.PS256) + || alg.equals(JWSAlgorithm.PS384) + || alg.equals(JWSAlgorithm.PS512)) { + + // asymmetric key + if (client.getJwks() != null) { + return jwksValidators.get(client.getJwks()); + } else if (!Strings.isNullOrEmpty(client.getJwksUri())) { + return jwksUriCache.getValidator(client.getJwksUri()); + } else { + return null; + } + + } else if (alg.equals(JWSAlgorithm.HS256) + || alg.equals(JWSAlgorithm.HS384) + || alg.equals(JWSAlgorithm.HS512)) { + + // symmetric key + + return symmetricCache.getSymmetricValidtor(client); + + } else { + + return null; + } + } catch (UncheckedExecutionException | ExecutionException e) { + logger.error("Problem loading client validator", e); + return null; + } + + } + + public JWTEncryptionAndDecryptionService getEncrypter(ClientDetailsEntity client) { + + try { + if (client.getJwks() != null) { + return jwksEncrypters.get(client.getJwks()); + } else if (!Strings.isNullOrEmpty(client.getJwksUri())) { + return jwksUriCache.getEncrypter(client.getJwksUri()); + } else { + return null; + } + } catch (UncheckedExecutionException | ExecutionException e) { + logger.error("Problem loading client encrypter", e); + return null; + } + + } + + + private class JWKSetEncryptorBuilder extends CacheLoader { + + @Override + public JWTEncryptionAndDecryptionService load(JWKSet key) throws Exception { + return new DefaultJWTEncryptionAndDecryptionService(new JWKSetKeyStore(key)); + } + + } + + private class JWKSetVerifierBuilder extends CacheLoader { + + @Override + public JWTSigningAndValidationService load(JWKSet key) throws Exception { + return new DefaultJWTSigningAndValidationService(new JWKSetKeyStore(key)); + } + + } + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJwtSigningAndValidationService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java similarity index 69% rename from openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJwtSigningAndValidationService.java rename to openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java index e9fc720d72..eea51fcfd2 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJwtSigningAndValidationService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/DefaultJWTSigningAndValidationService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.jwt.signer.service.impl; import java.security.NoSuchAlgorithmException; @@ -23,9 +24,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,6 +36,8 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSSigner; import com.nimbusds.jose.JWSVerifier; +import com.nimbusds.jose.crypto.ECDSASigner; +import com.nimbusds.jose.crypto.ECDSAVerifier; import com.nimbusds.jose.crypto.MACSigner; import com.nimbusds.jose.crypto.MACVerifier; import com.nimbusds.jose.crypto.RSASSASigner; @@ -44,36 +48,39 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jwt.SignedJWT; -public class DefaultJwtSigningAndValidationService implements JwtSigningAndValidationService { +public class DefaultJWTSigningAndValidationService implements JWTSigningAndValidationService { // map of identifier to signer - private Map signers = new HashMap(); + private Map signers = new HashMap<>(); // map of identifier to verifier - private Map verifiers = new HashMap(); + private Map verifiers = new HashMap<>(); - private static Logger logger = LoggerFactory.getLogger(DefaultJwtSigningAndValidationService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(DefaultJWTSigningAndValidationService.class); private String defaultSignerKeyId; private JWSAlgorithm defaultAlgorithm; // map of identifier to key - private Map keys = new HashMap(); + private Map keys = new HashMap<>(); /** * Build this service based on the keys given. All public keys will be used * to make verifiers, all private keys will be used to make signers. - * + * * @param keys * A map of key identifier to key - * + * * @throws InvalidKeySpecException * If the keys in the JWKs are not valid * @throws NoSuchAlgorithmException * If there is no appropriate algorithm to tie the keys to. */ - public DefaultJwtSigningAndValidationService(Map keys) throws NoSuchAlgorithmException, InvalidKeySpecException { + public DefaultJWTSigningAndValidationService(Map keys) throws NoSuchAlgorithmException, InvalidKeySpecException { this.keys = keys; buildSignersAndVerifiers(); } @@ -81,23 +88,26 @@ public DefaultJwtSigningAndValidationService(Map keys) throws NoSuc /** * Build this service based on the given keystore. All keys must have a key * id ({@code kid}) field in order to be used. - * + * * @param keyStore * the keystore to load all keys from - * + * * @throws InvalidKeySpecException * If the keys in the JWKs are not valid * @throws NoSuchAlgorithmException * If there is no appropriate algorithm to tie the keys to. */ - public DefaultJwtSigningAndValidationService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException { + public DefaultJWTSigningAndValidationService(JWKSetKeyStore keyStore) throws NoSuchAlgorithmException, InvalidKeySpecException { // convert all keys in the keystore to a map based on key id if (keyStore!= null && keyStore.getJwkSet() != null) { for (JWK key : keyStore.getKeys()) { if (!Strings.isNullOrEmpty(key.getKeyID())) { + // use the key ID that's built into the key itself this.keys.put(key.getKeyID(), key); } else { - throw new IllegalArgumentException("Tried to load a key from a keystore without a 'kid' field: " + key); + // create a random key id + String fakeKid = UUID.randomUUID().toString(); + this.keys.put(fakeKid, key); } } } @@ -108,15 +118,9 @@ public DefaultJwtSigningAndValidationService(JWKSetKeyStore keyStore) throws NoS /** * @return the defaultSignerKeyId */ + @Override public String getDefaultSignerKeyId() { - if (defaultSignerKeyId != null) { - return defaultSignerKeyId; - } else if (keys.size() == 1) { - // if there's only one key, it's the default - return keys.keySet().iterator().next(); - } else { - return null; - } + return defaultSignerKeyId; } /** @@ -157,38 +161,52 @@ private void buildSignersAndVerifiers() throws NoSuchAlgorithmException, Invalid String id = jwkEntry.getKey(); JWK jwk = jwkEntry.getValue(); - if (jwk instanceof RSAKey) { - // build RSA signers & verifiers + try { + if (jwk instanceof RSAKey) { + // build RSA signers & verifiers - if (jwk.isPrivate()) { // only add the signer if there's a private key - RSASSASigner signer = new RSASSASigner(((RSAKey) jwk).toRSAPrivateKey()); - signers.put(id, signer); - } + if (jwk.isPrivate()) { // only add the signer if there's a private key + RSASSASigner signer = new RSASSASigner((RSAKey) jwk); + signers.put(id, signer); + } - RSASSAVerifier verifier = new RSASSAVerifier(((RSAKey) jwk).toRSAPublicKey()); - verifiers.put(id, verifier); + RSASSAVerifier verifier = new RSASSAVerifier((RSAKey) jwk); + verifiers.put(id, verifier); - } else if (jwk instanceof ECKey) { - // build EC signers & verifiers + } else if (jwk instanceof ECKey) { + // build EC signers & verifiers - // TODO: add support for EC keys - logger.warn("EC Keys are not yet supported."); + if (jwk.isPrivate()) { + ECDSASigner signer = new ECDSASigner((ECKey) jwk); + signers.put(id, signer); + } - } else if (jwk instanceof OctetSequenceKey) { - // build HMAC signers & verifiers + ECDSAVerifier verifier = new ECDSAVerifier((ECKey) jwk); + verifiers.put(id, verifier); - if (jwk.isPrivate()) { // technically redundant check because all HMAC keys are private - MACSigner signer = new MACSigner(((OctetSequenceKey) jwk).toByteArray()); - signers.put(id, signer); - } + } else if (jwk instanceof OctetSequenceKey) { + // build HMAC signers & verifiers + + if (jwk.isPrivate()) { // technically redundant check because all HMAC keys are private + MACSigner signer = new MACSigner((OctetSequenceKey) jwk); + signers.put(id, signer); + } - MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray()); - verifiers.put(id, verifier); + MACVerifier verifier = new MACVerifier((OctetSequenceKey) jwk); + verifiers.put(id, verifier); - } else { - logger.warn("Unknown key type: " + jwk); + } else { + logger.warn("Unknown key type: " + jwk); + } + } catch (JOSEException e) { + logger.warn("Exception loading signer/verifier", e); } } + + if (defaultSignerKeyId == null && keys.size() == 1) { + // if there's only one key, it's the default + setDefaultSignerKeyId(keys.keySet().iterator().next()); + } } /** @@ -217,7 +235,7 @@ public void signJwt(SignedJWT jwt, JWSAlgorithm alg) { JWSSigner signer = null; for (JWSSigner s : signers.values()) { - if (s.supportedAlgorithms().contains(alg)) { + if (s.supportedJWSAlgorithms().contains(alg)) { signer = s; break; } @@ -248,7 +266,7 @@ public boolean validateSignature(SignedJWT jwt) { } } catch (JOSEException e) { - logger.error("Failed to validate signature, error was: ", e); + logger.error("Failed to validate signature with " + verifier + " error message: " + e.getMessage()); } } return false; @@ -256,7 +274,7 @@ public boolean validateSignature(SignedJWT jwt) { @Override public Map getAllPublicKeys() { - Map pubKeys = new HashMap(); + Map pubKeys = new HashMap<>(); // pull all keys out of the verifiers if we know how for (String keyId : keys.keySet()) { @@ -276,14 +294,14 @@ public Map getAllPublicKeys() { @Override public Collection getAllSigningAlgsSupported() { - Set algs = new HashSet(); + Set algs = new HashSet<>(); for (JWSSigner signer : signers.values()) { - algs.addAll(signer.supportedAlgorithms()); + algs.addAll(signer.supportedJWSAlgorithms()); } for (JWSVerifier verifier : verifiers.values()) { - algs.addAll(verifier.supportedAlgorithms()); + algs.addAll(verifier.supportedJWSAlgorithms()); } return algs; diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java index dc26461a49..8c98005115 100644 --- a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/JWKSetCacheService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.jwt.signer.service.impl; @@ -23,51 +24,56 @@ import java.util.concurrent.TimeUnit; import org.apache.http.client.HttpClient; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.client.SystemDefaultHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.jose.keystore.JWKSetKeyStore; -import org.mitre.jwt.encryption.service.JwtEncryptionAndDecryptionService; -import org.mitre.jwt.encryption.service.impl.DefaultJwtEncryptionAndDecryptionService; -import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; +import org.mitre.jwt.encryption.service.impl.DefaultJWTEncryptionAndDecryptionService; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.stereotype.Service; +import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.gson.JsonParseException; import com.nimbusds.jose.jwk.JWKSet; /** - * + * * Creates a caching map of JOSE signers/validators and encrypters/decryptors * keyed on the JWK Set URI. Dynamically loads JWK Sets to create the services. - * + * * @author jricher - * + * */ @Service public class JWKSetCacheService { - private static Logger logger = LoggerFactory.getLogger(JWKSetCacheService.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(JWKSetCacheService.class); // map of jwk set uri -> signing/validation service built on the keys found in that jwk set - private LoadingCache validators; + private LoadingCache validators; // map of jwk set uri -> encryption/decryption service built on the keys found in that jwk set - private LoadingCache encrypters; + private LoadingCache encrypters; public JWKSetCacheService() { this.validators = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch .maximumSize(100) - .build(new JWKSetVerifierFetcher()); + .build(new JWKSetVerifierFetcher(HttpClientBuilder.create().useSystemProperties().build())); this.encrypters = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) // expires 1 hour after fetch .maximumSize(100) - .build(new JWKSetEncryptorFetcher()); + .build(new JWKSetEncryptorFetcher(HttpClientBuilder.create().useSystemProperties().build())); } /** @@ -76,20 +82,20 @@ public JWKSetCacheService() { * @throws ExecutionException * @see com.google.common.cache.Cache#get(java.lang.Object) */ - public JwtSigningAndValidationService getValidator(String jwksUri) { + public JWTSigningAndValidationService getValidator(String jwksUri) { try { return validators.get(jwksUri); - } catch (ExecutionException e) { - logger.warn("Couldn't load JWK Set from " + jwksUri, e); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage()); return null; } } - public JwtEncryptionAndDecryptionService getEncrypter(String jwksUri) { + public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) { try { return encrypters.get(jwksUri); - } catch (ExecutionException e) { - logger.warn("Couldn't load JWK Set from " + jwksUri, e); + } catch (UncheckedExecutionException | ExecutionException e) { + logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage()); return null; } } @@ -98,51 +104,62 @@ public JwtEncryptionAndDecryptionService getEncrypter(String jwksUri) { * @author jricher * */ - private class JWKSetVerifierFetcher extends CacheLoader { - private HttpClient httpClient = new SystemDefaultHttpClient(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - private RestTemplate restTemplate = new RestTemplate(httpFactory); + private class JWKSetVerifierFetcher extends CacheLoader { + private HttpComponentsClientHttpRequestFactory httpFactory; + private RestTemplate restTemplate; + + JWKSetVerifierFetcher(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + this.restTemplate = new RestTemplate(httpFactory); + } /** * Load the JWK Set and build the appropriate signing service. */ @Override - public JwtSigningAndValidationService load(String key) throws Exception { - + public JWTSigningAndValidationService load(String key) throws Exception { String jsonString = restTemplate.getForObject(key, String.class); JWKSet jwkSet = JWKSet.parse(jsonString); JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); - JwtSigningAndValidationService service = new DefaultJwtSigningAndValidationService(keyStore); + JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keyStore); return service; - } } /** - * @author jricher - * - */ - private class JWKSetEncryptorFetcher extends CacheLoader { - private HttpClient httpClient = new SystemDefaultHttpClient(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - private RestTemplate restTemplate = new RestTemplate(httpFactory); + * @author jricher + * + */ + private class JWKSetEncryptorFetcher extends CacheLoader { + private HttpComponentsClientHttpRequestFactory httpFactory; + private RestTemplate restTemplate; + + public JWKSetEncryptorFetcher(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + this.restTemplate = new RestTemplate(httpFactory); + } + /* (non-Javadoc) * @see com.google.common.cache.CacheLoader#load(java.lang.Object) */ @Override - public JwtEncryptionAndDecryptionService load(String key) throws Exception { - String jsonString = restTemplate.getForObject(key, String.class); - JWKSet jwkSet = JWKSet.parse(jsonString); + public JWTEncryptionAndDecryptionService load(String key) throws Exception { + try { + String jsonString = restTemplate.getForObject(key, String.class); + JWKSet jwkSet = JWKSet.parse(jsonString); - JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); + JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); - JwtEncryptionAndDecryptionService service = new DefaultJwtEncryptionAndDecryptionService(keyStore); + JWTEncryptionAndDecryptionService service = new DefaultJWTEncryptionAndDecryptionService(keyStore); - return service; + return service; + } catch (JsonParseException | RestClientException e) { + throw new IllegalArgumentException("Unable to load JWK Set"); + } } } diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java new file mode 100644 index 0000000000..817cffefce --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/jwt/signer/service/impl/SymmetricKeyJWTValidatorCacheService.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.jwt.signer.service.impl; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.UncheckedExecutionException; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.util.Base64URL; + +/** + * Creates and caches symmetrical validators for clients based on client secrets. + * + * @author jricher + * + */ +@Service +public class SymmetricKeyJWTValidatorCacheService { + + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(SymmetricKeyJWTValidatorCacheService.class); + + private LoadingCache validators; + + + public SymmetricKeyJWTValidatorCacheService() { + validators = CacheBuilder.newBuilder() + .expireAfterAccess(24, TimeUnit.HOURS) + .maximumSize(100) + .build(new SymmetricValidatorBuilder()); + } + + + /** + * Create a symmetric signing and validation service for the given client + * + * @param client + * @return + */ + public JWTSigningAndValidationService getSymmetricValidtor(ClientDetailsEntity client) { + + if (client == null) { + logger.error("Couldn't create symmetric validator for null client"); + return null; + } + + if (Strings.isNullOrEmpty(client.getClientSecret())) { + logger.error("Couldn't create symmetric validator for client " + client.getClientId() + " without a client secret"); + return null; + } + + try { + return validators.get(client.getClientSecret()); + } catch (UncheckedExecutionException ue) { + logger.error("Problem loading client validator", ue); + return null; + } catch (ExecutionException e) { + logger.error("Problem loading client validator", e); + return null; + } + + } + + public class SymmetricValidatorBuilder extends CacheLoader { + @Override + public JWTSigningAndValidationService load(String key) throws Exception { + try { + + String id = "SYMMETRIC-KEY"; + JWK jwk = new OctetSequenceKey.Builder(Base64URL.encode(key)) + .keyUse(KeyUse.SIGNATURE) + .keyID(id) + .build(); + Map keys = ImmutableMap.of(id, jwk); + JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keys); + + return service; + + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + logger.error("Couldn't create symmetric validator for client", e); + } + + throw new IllegalArgumentException("Couldn't create symmetric validator for client"); + } + + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/exception/DeviceCodeCreationException.java b/openid-connect-common/src/main/java/org/mitre/oauth2/exception/DeviceCodeCreationException.java new file mode 100644 index 0000000000..9924505071 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/exception/DeviceCodeCreationException.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.exception; + +/** + * @author jricher + * + */ +public class DeviceCodeCreationException extends Exception { + + private static final long serialVersionUID = 8078568710169208466L; + + private String error; + + public DeviceCodeCreationException(String error, String message) { + super(message); + this.error = error; + } + + /** + * @return the error + */ + public String getError() { + return error; + } + + /** + * @param error the error to set + */ + public void setError(String error) { + this.error = error; + } + + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java index dfbfa8d339..28accd47e7 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthenticationHolderEntity.java @@ -1,47 +1,89 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.model; +import java.io.Serializable; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.Lob; +import javax.persistence.JoinColumn; +import javax.persistence.MapKeyColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; +import javax.persistence.OneToOne; import javax.persistence.Table; +import javax.persistence.Transient; +import org.mitre.oauth2.model.convert.SerializableStringConverter; +import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; @Entity @Table(name = "authentication_holder") @NamedQueries ({ - @NamedQuery(name = "AuthenticationHolderEntity.getByAuthentication", query = "select a from AuthenticationHolderEntity a where a.authentication = :authentication") + @NamedQuery(name = AuthenticationHolderEntity.QUERY_ALL, query = "select a from AuthenticationHolderEntity a"), + @NamedQuery(name = AuthenticationHolderEntity.QUERY_GET_UNUSED, query = "select a from AuthenticationHolderEntity a where " + + "a.id not in (select t.authenticationHolder.id from OAuth2AccessTokenEntity t) and " + + "a.id not in (select r.authenticationHolder.id from OAuth2RefreshTokenEntity r) and " + + "a.id not in (select c.authenticationHolder.id from AuthorizationCodeEntity c)") }) public class AuthenticationHolderEntity { + public static final String QUERY_GET_UNUSED = "AuthenticationHolderEntity.getUnusedAuthenticationHolders"; + public static final String QUERY_ALL = "AuthenticationHolderEntity.getAll"; + private Long id; - private Long ownerId; + private SavedUserAuthentication userAuth; + + private Collection authorities; + + private Set resourceIds; + + private boolean approved; + + private String redirectUri; + + private Set responseTypes; - private OAuth2Authentication authentication; + private Map extensions; + + private String clientId; + + private Set scope; + + private Map requestParameters; public AuthenticationHolderEntity() { @@ -58,25 +100,226 @@ public void setId(Long id) { this.id = id; } + @Transient + public OAuth2Authentication getAuthentication() { + // TODO: memoize this + return new OAuth2Authentication(createOAuth2Request(), getUserAuth()); + } + + /** + * @return + */ + private OAuth2Request createOAuth2Request() { + return new OAuth2Request(requestParameters, clientId, authorities, approved, scope, resourceIds, redirectUri, responseTypes, extensions); + } + + public void setAuthentication(OAuth2Authentication authentication) { + + // pull apart the request and save its bits + OAuth2Request o2Request = authentication.getOAuth2Request(); + setAuthorities(o2Request.getAuthorities() == null ? null : new HashSet<>(o2Request.getAuthorities())); + setClientId(o2Request.getClientId()); + setExtensions(o2Request.getExtensions() == null ? null : new HashMap<>(o2Request.getExtensions())); + setRedirectUri(o2Request.getRedirectUri()); + setRequestParameters(o2Request.getRequestParameters() == null ? null : new HashMap<>(o2Request.getRequestParameters())); + setResourceIds(o2Request.getResourceIds() == null ? null : new HashSet<>(o2Request.getResourceIds())); + setResponseTypes(o2Request.getResponseTypes() == null ? null : new HashSet<>(o2Request.getResponseTypes())); + setScope(o2Request.getScope() == null ? null : new HashSet<>(o2Request.getScope())); + setApproved(o2Request.isApproved()); + + if (authentication.getUserAuthentication() != null) { + this.userAuth = new SavedUserAuthentication(authentication.getUserAuthentication()); + } else { + this.userAuth = null; + } + } + + /** + * @return the userAuth + */ + @OneToOne(cascade=CascadeType.ALL) + @JoinColumn(name = "user_auth_id") + public SavedUserAuthentication getUserAuth() { + return userAuth; + } + + /** + * @param userAuth the userAuth to set + */ + public void setUserAuth(SavedUserAuthentication userAuth) { + this.userAuth = userAuth; + } + + /** + * @return the authorities + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="authentication_holder_authority", + joinColumns=@JoinColumn(name="owner_id") + ) + @Convert(converter = SimpleGrantedAuthorityStringConverter.class) + @Column(name="authority") + public Collection getAuthorities() { + return authorities; + } + + /** + * @param authorities the authorities to set + */ + public void setAuthorities(Collection authorities) { + this.authorities = authorities; + } + + /** + * @return the resourceIds + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="authentication_holder_resource_id", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="resource_id") + public Set getResourceIds() { + return resourceIds; + } + + /** + * @param resourceIds the resourceIds to set + */ + public void setResourceIds(Set resourceIds) { + this.resourceIds = resourceIds; + } + + /** + * @return the approved + */ @Basic - @Column(name = "owner_id") - public Long getOwnerId() { - return ownerId; + @Column(name="approved") + public boolean isApproved() { + return approved; } - public void setOwnerId(Long owner_id) { - this.ownerId = owner_id; + /** + * @param approved the approved to set + */ + public void setApproved(boolean approved) { + this.approved = approved; } - @Lob - @Basic(fetch=FetchType.LAZY) - @Column(name = "authentication") - public OAuth2Authentication getAuthentication() { - return authentication; + /** + * @return the redirectUri + */ + @Basic + @Column(name="redirect_uri") + public String getRedirectUri() { + return redirectUri; } - public void setAuthentication(OAuth2Authentication authentication) { - this.authentication = authentication; + /** + * @param redirectUri the redirectUri to set + */ + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + + /** + * @return the responseTypes + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="authentication_holder_response_type", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="response_type") + public Set getResponseTypes() { + return responseTypes; + } + + /** + * @param responseTypes the responseTypes to set + */ + public void setResponseTypes(Set responseTypes) { + this.responseTypes = responseTypes; + } + + /** + * @return the extensions + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="authentication_holder_extension", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="val") + @MapKeyColumn(name="extension") + @Convert(converter=SerializableStringConverter.class) + public Map getExtensions() { + return extensions; + } + + /** + * @param extensions the extensions to set + */ + public void setExtensions(Map extensions) { + this.extensions = extensions; + } + + /** + * @return the clientId + */ + @Basic + @Column(name="client_id") + public String getClientId() { + return clientId; + } + + /** + * @param clientId the clientId to set + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * @return the scope + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="authentication_holder_scope", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="scope") + public Set getScope() { + return scope; + } + + /** + * @param scope the scope to set + */ + public void setScope(Set scope) { + this.scope = scope; + } + + /** + * @return the requestParameters + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="authentication_holder_request_parameter", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="val") + @MapKeyColumn(name="param") + public Map getRequestParameters() { + return requestParameters; + } + + /** + * @param requestParameters the requestParameters to set + */ + public void setRequestParameters(Map requestParameters) { + this.requestParameters = requestParameters; } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java index 7411ba5e53..385f467685 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/AuthorizationCodeEntity.java @@ -1,53 +1,63 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.model; +import java.util.Date; + import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; -import javax.persistence.Lob; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; - -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import javax.persistence.Temporal; /** * Entity class for authorization codes - * + * * @author aanganes * */ @Entity @Table(name = "authorization_code") @NamedQueries({ - @NamedQuery(name = "AuthorizationCodeEntity.getByValue", query = "select a from AuthorizationCodeEntity a where a.code = :code") + @NamedQuery(name = AuthorizationCodeEntity.QUERY_BY_VALUE, query = "select a from AuthorizationCodeEntity a where a.code = :code"), + @NamedQuery(name = AuthorizationCodeEntity.QUERY_EXPIRATION_BY_DATE, query = "select a from AuthorizationCodeEntity a where a.expiration <= :" + AuthorizationCodeEntity.PARAM_DATE) }) public class AuthorizationCodeEntity { + public static final String QUERY_BY_VALUE = "AuthorizationCodeEntity.getByValue"; + public static final String QUERY_EXPIRATION_BY_DATE = "AuthorizationCodeEntity.expirationByDate"; + + public static final String PARAM_DATE = "date"; + private Long id; private String code; - private OAuth2Authentication authentication; + private AuthenticationHolderEntity authenticationHolder; + + private Date expiration; /** * Default constructor. @@ -58,13 +68,14 @@ public AuthorizationCodeEntity() { /** * Create a new AuthorizationCodeEntity with the given code and AuthorizationRequestHolder. - * + * * @param code the authorization code * @param authRequest the AuthoriztionRequestHolder associated with the original code request */ - public AuthorizationCodeEntity(String code, OAuth2Authentication authRequest) { + public AuthorizationCodeEntity(String code, AuthenticationHolderEntity authenticationHolder, Date expiration) { this.code = code; - this.authentication = authRequest; + this.authenticationHolder = authenticationHolder; + this.expiration = expiration; } /** @@ -101,20 +112,30 @@ public void setCode(String code) { } /** + * The authentication in place when this token was created. * @return the authentication */ - @Lob - @Basic(fetch=FetchType.EAGER) - @Column(name="authentication") - public OAuth2Authentication getAuthentication() { - return authentication; + @ManyToOne + @JoinColumn(name = "auth_holder_id") + public AuthenticationHolderEntity getAuthenticationHolder() { + return authenticationHolder; } /** * @param authentication the authentication to set */ - public void setAuthentication(OAuth2Authentication authentication) { - this.authentication = authentication; + public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHolder) { + this.authenticationHolder = authenticationHolder; } + @Basic + @Temporal(javax.persistence.TemporalType.TIMESTAMP) + @Column(name = "expiration") + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java index 20f53f4234..c161c07970 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/ClientDetailsEntity.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -25,13 +26,11 @@ import java.util.Map; import java.util.Set; -import javax.persistence.AttributeOverride; -import javax.persistence.AttributeOverrides; import javax.persistence.Basic; import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.ElementCollection; -import javax.persistence.Embedded; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -49,31 +48,39 @@ import javax.persistence.TemporalType; import javax.persistence.Transient; -import org.mitre.jose.JWEAlgorithmEmbed; -import org.mitre.jose.JWEEncryptionMethodEmbed; -import org.mitre.jose.JWSAlgorithmEmbed; +import org.mitre.oauth2.model.convert.JWEAlgorithmStringConverter; +import org.mitre.oauth2.model.convert.JWEEncryptionMethodStringConverter; +import org.mitre.oauth2.model.convert.JWKSetStringConverter; +import org.mitre.oauth2.model.convert.JWSAlgorithmStringConverter; +import org.mitre.oauth2.model.convert.JWTStringConverter; +import org.mitre.oauth2.model.convert.PKCEAlgorithmStringConverter; +import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.provider.ClientDetails; import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; /** * @author jricher - * + * */ @Entity @Table(name = "client_details") @NamedQueries({ - @NamedQuery(name = "ClientDetailsEntity.findAll", query = "SELECT c FROM ClientDetailsEntity c"), - @NamedQuery(name = "ClientDetailsEntity.getByClientId", query = "select c from ClientDetailsEntity c where c.clientId = :clientId") + @NamedQuery(name = ClientDetailsEntity.QUERY_ALL, query = "SELECT c FROM ClientDetailsEntity c"), + @NamedQuery(name = ClientDetailsEntity.QUERY_BY_CLIENT_ID, query = "select c from ClientDetailsEntity c where c.clientId = :" + ClientDetailsEntity.PARAM_CLIENT_ID) }) public class ClientDetailsEntity implements ClientDetails { - /** - * - */ + public static final String QUERY_BY_CLIENT_ID = "ClientDetailsEntity.getByClientId"; + public static final String QUERY_ALL = "ClientDetailsEntity.findAll"; + + public static final String PARAM_CLIENT_ID = "clientId"; + private static final int DEFAULT_ID_TOKEN_VALIDITY_SECONDS = 600; private static final long serialVersionUID = -1617727085733786296L; @@ -83,51 +90,54 @@ public class ClientDetailsEntity implements ClientDetails { /** Fields from the OAuth2 Dynamic Registration Specification */ private String clientId = null; // client_id private String clientSecret = null; // client_secret - private Set redirectUris = new HashSet(); // redirect_uris + private Set redirectUris = new HashSet<>(); // redirect_uris private String clientName; // client_name private String clientUri; // client_uri private String logoUri; // logo_uri private Set contacts; // contacts private String tosUri; // tos_uri private AuthMethod tokenEndpointAuthMethod = AuthMethod.SECRET_BASIC; // token_endpoint_auth_method - private Set scope = new HashSet(); // scope - private Set grantTypes = new HashSet(); // grant_types - private Set responseTypes = new HashSet(); // response_types + private Set scope = new HashSet<>(); // scope + private Set grantTypes = new HashSet<>(); // grant_types + private Set responseTypes = new HashSet<>(); // response_types private String policyUri; - private String jwksUri; + private String jwksUri; // URI pointer to keys + private JWKSet jwks; // public key stored by value + private String softwareId; + private String softwareVersion; /** Fields from OIDC Client Registration Specification **/ private AppType applicationType; // application_type private String sectorIdentifierUri; // sector_identifier_uri private SubjectType subjectType; // subject_type - private JWSAlgorithmEmbed requestObjectSigningAlg = null; // request_object_signing_alg + private JWSAlgorithm requestObjectSigningAlg = null; // request_object_signing_alg - private JWSAlgorithmEmbed userInfoSignedResponseAlg = null; // user_info_signed_response_alg - private JWEAlgorithmEmbed userInfoEncryptedResponseAlg = null; // user_info_encrypted_response_alg - private JWEEncryptionMethodEmbed userInfoEncryptedResponseEnc = null; // user_info_encrypted_response_enc + private JWSAlgorithm userInfoSignedResponseAlg = null; // user_info_signed_response_alg + private JWEAlgorithm userInfoEncryptedResponseAlg = null; // user_info_encrypted_response_alg + private EncryptionMethod userInfoEncryptedResponseEnc = null; // user_info_encrypted_response_enc - private JWSAlgorithmEmbed idTokenSignedResponseAlg = null; // id_token_signed_response_alg - private JWEAlgorithmEmbed idTokenEncryptedResponseAlg = null; // id_token_encrypted_response_alg - private JWEEncryptionMethodEmbed idTokenEncryptedResponseEnc = null; // id_token_encrypted_response_enc + private JWSAlgorithm idTokenSignedResponseAlg = null; // id_token_signed_response_alg + private JWEAlgorithm idTokenEncryptedResponseAlg = null; // id_token_encrypted_response_alg + private EncryptionMethod idTokenEncryptedResponseEnc = null; // id_token_encrypted_response_enc - private JWSAlgorithmEmbed tokenEndpointAuthSigningAlg = null; // token_endpoint_auth_signing_alg + private JWSAlgorithm tokenEndpointAuthSigningAlg = null; // token_endpoint_auth_signing_alg private Integer defaultMaxAge; // default_max_age private Boolean requireAuthTime; // require_auth_time private Set defaultACRvalues; // default_acr_values private String initiateLoginUri; // initiate_login_uri - private String postLogoutRedirectUri; // post_logout_redirect_uri + private Set postLogoutRedirectUris; // post_logout_redirect_uris private Set requestUris; // request_uris /** Fields to support the ClientDetails interface **/ - private Set authorities = new HashSet(); + private Set authorities = new HashSet<>(); private Integer accessTokenValiditySeconds = 0; // in seconds private Integer refreshTokenValiditySeconds = 0; // in seconds - private Set resourceIds = new HashSet(); - private Map additionalInformation = new HashMap(); + private Set resourceIds = new HashSet<>(); + private Map additionalInformation = new HashMap<>(); /** Our own fields **/ private String clientDescription = ""; // human-readable description @@ -136,6 +146,17 @@ public class ClientDetailsEntity implements ClientDetails { private boolean allowIntrospection = false; // do we let this client call the introspection endpoint? private Integer idTokenValiditySeconds; //timeout for id tokens private Date createdAt; // time the client was created + private boolean clearAccessTokensOnRefresh = true; // do we clear access tokens on refresh? + private Integer deviceCodeValiditySeconds; // timeout for device codes + + /** fields for UMA */ + private Set claimsRedirectUris; + + /** Software statement **/ + private JWT softwareStatement; + + /** PKCE **/ + private PKCEAlgorithm codeChallengeMethod; public enum AuthMethod { SECRET_POST("client_secret_post"), @@ -147,7 +168,7 @@ public enum AuthMethod { private final String value; // map to aid reverse lookup - private static final Map lookup = new HashMap(); + private static final Map lookup = new HashMap<>(); static { for (AuthMethod a : AuthMethod.values()) { lookup.put(a.getValue(), a); @@ -173,7 +194,7 @@ public enum AppType { private final String value; // map to aid reverse lookup - private static final Map lookup = new HashMap(); + private static final Map lookup = new HashMap<>(); static { for (AppType a : AppType.values()) { lookup.put(a.getValue(), a); @@ -199,7 +220,7 @@ public enum SubjectType { private final String value; // map to aid reverse lookup - private static final Map lookup = new HashMap(); + private static final Map lookup = new HashMap<>(); static { for (SubjectType u : SubjectType.values()) { lookup.put(u.getValue(), u); @@ -246,7 +267,7 @@ public Long getId() { } /** - * + * * @param id the id to set */ public void setId(Long id) { @@ -293,7 +314,7 @@ public void setReuseRefreshToken(boolean reuseRefreshToken) { /** * Number of seconds ID token is valid for. MUST be a positive integer, can not be null. - * + * * @return the idTokenValiditySeconds */ @Basic @@ -346,13 +367,20 @@ public void setAllowIntrospection(boolean allowIntrospection) { } /** - * + * */ @Override @Transient public boolean isSecretRequired() { - // TODO: this should check the auth method field instead - return getClientSecret() != null; + if (getTokenEndpointAuthMethod() != null && + (getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC) || + getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST) || + getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT))) { + return true; + } else { + return false; + } + } /** @@ -443,6 +471,7 @@ public void setGrantTypes(Set grantTypes) { * passthrough for SECOAUTH api */ @Override + @Transient public Set getAuthorizedGrantTypes() { return getGrantTypes(); } @@ -456,6 +485,7 @@ public Set getAuthorizedGrantTypes() { joinColumns=@JoinColumn(name="owner_id") ) @Override + @Convert(converter = SimpleGrantedAuthorityStringConverter.class) @Column(name="authority") public Set getAuthorities() { return authorities; @@ -550,9 +580,9 @@ public void setResourceIds(Set resourceIds) { /** * This library does not make use of this field, so it is not * stored using our persistence layer. - * + * * However, it's somehow required by SECOUATH. - * + * * @return an empty map */ @Override @@ -680,6 +710,23 @@ public void setJwksUri(String jwksUri) { this.jwksUri = jwksUri; } + /** + * @return the jwks + */ + @Basic + @Column(name="jwks") + @Convert(converter = JWKSetStringConverter.class) + public JWKSet getJwks() { + return jwks; + } + + /** + * @param jwks the jwks to set + */ + public void setJwks(JWKSet jwks) { + this.jwks = jwks; + } + @Basic @Column(name="sector_identifier_uri") public String getSectorIdentifierUri() { @@ -690,212 +737,94 @@ public void setSectorIdentifierUri(String sectorIdentifierUri) { this.sectorIdentifierUri = sectorIdentifierUri; } - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="request_object_signing_alg")) - }) - public JWSAlgorithmEmbed getRequestObjectSigningAlgEmbed() { - return requestObjectSigningAlg; - } - - public void setRequestObjectSigningAlgEmbed(JWSAlgorithmEmbed requestObjectSigningAlg) { - this.requestObjectSigningAlg = requestObjectSigningAlg; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="user_info_signed_response_alg")) - }) - public JWSAlgorithmEmbed getUserInfoSignedResponseAlgEmbed() { - return userInfoSignedResponseAlg; - } - - public void setUserInfoSignedResponseAlgEmbed(JWSAlgorithmEmbed userInfoSignedResponseAlg) { - this.userInfoSignedResponseAlg = userInfoSignedResponseAlg; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="user_info_encrypted_response_alg")) - }) - public JWEAlgorithmEmbed getUserInfoEncryptedResponseAlgEmbed() { - return userInfoEncryptedResponseAlg; - } - - public void setUserInfoEncryptedResponseAlgEmbed(JWEAlgorithmEmbed userInfoEncryptedResponseAlg) { - this.userInfoEncryptedResponseAlg = userInfoEncryptedResponseAlg; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="user_info_encrypted_response_enc")) - }) - public JWEEncryptionMethodEmbed getUserInfoEncryptedResponseEncEmbed() { - return userInfoEncryptedResponseEnc; - } - - public void setUserInfoEncryptedResponseEncEmbed(JWEEncryptionMethodEmbed userInfoEncryptedResponseEnc) { - this.userInfoEncryptedResponseEnc = userInfoEncryptedResponseEnc; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="id_token_signed_response_alg")) - }) - public JWSAlgorithmEmbed getIdTokenSignedResponseAlgEmbed() { - return idTokenSignedResponseAlg; - } - - public void setIdTokenSignedResponseAlgEmbed(JWSAlgorithmEmbed idTokenSignedResponseAlg) { - this.idTokenSignedResponseAlg = idTokenSignedResponseAlg; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="id_token_encrypted_response_alg")) - }) - public JWEAlgorithmEmbed getIdTokenEncryptedResponseAlgEmbed() { - return idTokenEncryptedResponseAlg; - } - - public void setIdTokenEncryptedResponseAlgEmbed(JWEAlgorithmEmbed idTokenEncryptedResponseAlg) { - this.idTokenEncryptedResponseAlg = idTokenEncryptedResponseAlg; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="id_token_encrypted_response_enc")) - }) - public JWEEncryptionMethodEmbed getIdTokenEncryptedResponseEncEmbed() { - return idTokenEncryptedResponseEnc; - } - - public void setIdTokenEncryptedResponseEncEmbed(JWEEncryptionMethodEmbed idTokenEncryptedResponseEnc) { - this.idTokenEncryptedResponseEnc = idTokenEncryptedResponseEnc; - } - - @Embedded - @AttributeOverrides({ - @AttributeOverride(name = "algorithmName", column=@Column(name="token_endpoint_auth_signing_alg")) - }) - public JWSAlgorithmEmbed getTokenEndpointAuthSigningAlgEmbed() { - return tokenEndpointAuthSigningAlg; - } - - public void setTokenEndpointAuthSigningAlgEmbed(JWSAlgorithmEmbed tokenEndpointAuthSigningAlgEmbed) { - this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlgEmbed; - } - - // - // Transient passthrough methods for JOSE elements - // - - @Transient + @Basic + @Column(name = "request_object_signing_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) public JWSAlgorithm getRequestObjectSigningAlg() { - if (requestObjectSigningAlg != null) { - return requestObjectSigningAlg.getAlgorithm(); - } else { - return null; - } + return requestObjectSigningAlg; } public void setRequestObjectSigningAlg(JWSAlgorithm requestObjectSigningAlg) { - this.requestObjectSigningAlg = new JWSAlgorithmEmbed(requestObjectSigningAlg); + this.requestObjectSigningAlg = requestObjectSigningAlg; } - @Transient + @Basic + @Column(name = "user_info_signed_response_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) public JWSAlgorithm getUserInfoSignedResponseAlg() { - if (userInfoSignedResponseAlg != null) { - return userInfoSignedResponseAlg.getAlgorithm(); - } else { - return null; - } + return userInfoSignedResponseAlg; } public void setUserInfoSignedResponseAlg(JWSAlgorithm userInfoSignedResponseAlg) { - this.userInfoSignedResponseAlg = new JWSAlgorithmEmbed(userInfoSignedResponseAlg); + this.userInfoSignedResponseAlg = userInfoSignedResponseAlg; } - @Transient + @Basic + @Column(name = "user_info_encrypted_response_alg") + @Convert(converter = JWEAlgorithmStringConverter.class) public JWEAlgorithm getUserInfoEncryptedResponseAlg() { - if (userInfoEncryptedResponseAlg != null) { - return userInfoEncryptedResponseAlg.getAlgorithm(); - } else { - return null; - } + return userInfoEncryptedResponseAlg; } public void setUserInfoEncryptedResponseAlg(JWEAlgorithm userInfoEncryptedResponseAlg) { - this.userInfoEncryptedResponseAlg = new JWEAlgorithmEmbed(userInfoEncryptedResponseAlg); + this.userInfoEncryptedResponseAlg = userInfoEncryptedResponseAlg; } - @Transient + @Basic + @Column(name = "user_info_encrypted_response_enc") + @Convert(converter = JWEEncryptionMethodStringConverter.class) public EncryptionMethod getUserInfoEncryptedResponseEnc() { - if (userInfoEncryptedResponseEnc != null) { - return userInfoEncryptedResponseEnc.getAlgorithm(); - } else { - return null; - } + return userInfoEncryptedResponseEnc; } public void setUserInfoEncryptedResponseEnc(EncryptionMethod userInfoEncryptedResponseEnc) { - this.userInfoEncryptedResponseEnc = new JWEEncryptionMethodEmbed(userInfoEncryptedResponseEnc); + this.userInfoEncryptedResponseEnc = userInfoEncryptedResponseEnc; } - @Transient + @Basic + @Column(name="id_token_signed_response_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) public JWSAlgorithm getIdTokenSignedResponseAlg() { - if (idTokenSignedResponseAlg != null) { - return idTokenSignedResponseAlg.getAlgorithm(); - } else { - return null; - } + return idTokenSignedResponseAlg; } public void setIdTokenSignedResponseAlg(JWSAlgorithm idTokenSignedResponseAlg) { - this.idTokenSignedResponseAlg = new JWSAlgorithmEmbed(idTokenSignedResponseAlg); + this.idTokenSignedResponseAlg = idTokenSignedResponseAlg; } - @Transient + @Basic + @Column(name = "id_token_encrypted_response_alg") + @Convert(converter = JWEAlgorithmStringConverter.class) public JWEAlgorithm getIdTokenEncryptedResponseAlg() { - if (idTokenEncryptedResponseAlg != null) { - return idTokenEncryptedResponseAlg.getAlgorithm(); - } else { - return null; - } + return idTokenEncryptedResponseAlg; } public void setIdTokenEncryptedResponseAlg(JWEAlgorithm idTokenEncryptedResponseAlg) { - this.idTokenEncryptedResponseAlg = new JWEAlgorithmEmbed(idTokenEncryptedResponseAlg); + this.idTokenEncryptedResponseAlg = idTokenEncryptedResponseAlg; } - @Transient + @Basic + @Column(name = "id_token_encrypted_response_enc") + @Convert(converter = JWEEncryptionMethodStringConverter.class) public EncryptionMethod getIdTokenEncryptedResponseEnc() { - if (idTokenEncryptedResponseEnc != null) { - return idTokenEncryptedResponseEnc.getAlgorithm(); - } else { - return null; - } + return idTokenEncryptedResponseEnc; } public void setIdTokenEncryptedResponseEnc(EncryptionMethod idTokenEncryptedResponseEnc) { - this.idTokenEncryptedResponseEnc = new JWEEncryptionMethodEmbed(idTokenEncryptedResponseEnc); + this.idTokenEncryptedResponseEnc = idTokenEncryptedResponseEnc; } - @Transient + @Basic + @Column(name="token_endpoint_auth_signing_alg") + @Convert(converter = JWSAlgorithmStringConverter.class) public JWSAlgorithm getTokenEndpointAuthSigningAlg() { - if (tokenEndpointAuthSigningAlg != null) { - return tokenEndpointAuthSigningAlg.getAlgorithm(); - } else { - return null; - } + return tokenEndpointAuthSigningAlg; } public void setTokenEndpointAuthSigningAlg(JWSAlgorithm tokenEndpointAuthSigningAlg) { - this.tokenEndpointAuthSigningAlg = new JWSAlgorithmEmbed(tokenEndpointAuthSigningAlg); + this.tokenEndpointAuthSigningAlg = tokenEndpointAuthSigningAlg; } - // END Transient JOSE methods - @Basic @Column(name="default_max_age") public Integer getDefaultMaxAge() { @@ -975,17 +904,21 @@ public void setInitiateLoginUri(String initiateLoginUri) { /** * @return the postLogoutRedirectUri */ - @Basic + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="client_post_logout_redirect_uri", + joinColumns=@JoinColumn(name="owner_id") + ) @Column(name="post_logout_redirect_uri") - public String getPostLogoutRedirectUri() { - return postLogoutRedirectUri; + public Set getPostLogoutRedirectUris() { + return postLogoutRedirectUris; } /** * @param postLogoutRedirectUri the postLogoutRedirectUri to set */ - public void setPostLogoutRedirectUri(String postLogoutRedirectUri) { - this.postLogoutRedirectUri = postLogoutRedirectUri; + public void setPostLogoutRedirectUris(Set postLogoutRedirectUri) { + this.postLogoutRedirectUris = postLogoutRedirectUri; } /** @@ -1032,4 +965,122 @@ public boolean isAutoApprove(String scope) { return false; } + /** + * @return the clearAccessTokensOnRefresh + */ + @Basic + @Column(name = "clear_access_tokens_on_refresh") + public boolean isClearAccessTokensOnRefresh() { + return clearAccessTokensOnRefresh; + } + + /** + * @param clearAccessTokensOnRefresh the clearAccessTokensOnRefresh to set + */ + public void setClearAccessTokensOnRefresh(boolean clearAccessTokensOnRefresh) { + this.clearAccessTokensOnRefresh = clearAccessTokensOnRefresh; + } + + /** + * @return the claimsRedirectUris + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="client_claims_redirect_uri", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="redirect_uri") + public Set getClaimsRedirectUris() { + return claimsRedirectUris; + } + + /** + * @param claimsRedirectUris the claimsRedirectUris to set + */ + public void setClaimsRedirectUris(Set claimsRedirectUris) { + this.claimsRedirectUris = claimsRedirectUris; + } + + /** + * @return the softwareStatement + */ + @Basic + @Column(name = "software_statement") + @Convert(converter = JWTStringConverter.class) + public JWT getSoftwareStatement() { + return softwareStatement; + } + + /** + * @param softwareStatement the softwareStatement to set + */ + public void setSoftwareStatement(JWT softwareStatement) { + this.softwareStatement = softwareStatement; + } + + /** + * @return the codeChallengeMethod + */ + @Basic + @Column(name = "code_challenge_method") + @Convert(converter = PKCEAlgorithmStringConverter.class) + public PKCEAlgorithm getCodeChallengeMethod() { + return codeChallengeMethod; + } + + /** + * @param codeChallengeMethod the codeChallengeMethod to set + */ + public void setCodeChallengeMethod(PKCEAlgorithm codeChallengeMethod) { + this.codeChallengeMethod = codeChallengeMethod; + } + + /** + * @return the deviceCodeValiditySeconds + */ + @Basic + @Column(name="device_code_validity_seconds") + public Integer getDeviceCodeValiditySeconds() { + return deviceCodeValiditySeconds; + } + + /** + * @param deviceCodeValiditySeconds the deviceCodeValiditySeconds to set + */ + public void setDeviceCodeValiditySeconds(Integer deviceCodeValiditySeconds) { + this.deviceCodeValiditySeconds = deviceCodeValiditySeconds; + } + + /** + * @return the softwareId + */ + @Basic + @Column(name="software_id") + public String getSoftwareId() { + return softwareId; + } + + /** + * @param softwareId the softwareId to set + */ + public void setSoftwareId(String softwareId) { + this.softwareId = softwareId; + } + + /** + * @return the softwareVersion + */ + @Basic + @Column(name="software_version") + public String getSoftwareVersion() { + return softwareVersion; + } + + /** + * @param softwareVersion the softwareVersion to set + */ + public void setSoftwareVersion(String softwareVersion) { + this.softwareVersion = softwareVersion; + } + } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/DeviceCode.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/DeviceCode.java new file mode 100644 index 0000000000..c15a95fe11 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/DeviceCode.java @@ -0,0 +1,234 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model; + +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import javax.persistence.Temporal; + +/** + * @author jricher + * + */ +@Entity +@Table(name = "device_code") +@NamedQueries({ + @NamedQuery(name = DeviceCode.QUERY_BY_USER_CODE, query = "select d from DeviceCode d where d.userCode = :" + DeviceCode.PARAM_USER_CODE), + @NamedQuery(name = DeviceCode.QUERY_BY_DEVICE_CODE, query = "select d from DeviceCode d where d.deviceCode = :" + DeviceCode.PARAM_DEVICE_CODE), + @NamedQuery(name = DeviceCode.QUERY_EXPIRED_BY_DATE, query = "select d from DeviceCode d where d.expiration <= :" + DeviceCode.PARAM_DATE) +}) +public class DeviceCode { + + public static final String QUERY_BY_USER_CODE = "DeviceCode.queryByUserCode"; + public static final String QUERY_BY_DEVICE_CODE = "DeviceCode.queryByDeviceCode"; + public static final String QUERY_EXPIRED_BY_DATE = "DeviceCode.queryExpiredByDate"; + + public static final String PARAM_USER_CODE = "userCode"; + public static final String PARAM_DEVICE_CODE = "deviceCode"; + public static final String PARAM_DATE = "date"; + + private Long id; + private String deviceCode; + private String userCode; + private Set scope; + private Date expiration; + private String clientId; + private Map requestParameters; + private boolean approved; + private AuthenticationHolderEntity authenticationHolder; + + public DeviceCode() { + + } + + public DeviceCode(String deviceCode, String userCode, Set scope, String clientId, Map params) { + this.deviceCode = deviceCode; + this.userCode = userCode; + this.scope = scope; + this.clientId = clientId; + this.requestParameters = params; + } + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the deviceCode + */ + @Basic + @Column(name = "device_code") + public String getDeviceCode() { + return deviceCode; + } + + /** + * @param deviceCode the deviceCode to set + */ + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } + + /** + * @return the userCode + */ + @Basic + @Column(name = "user_code") + public String getUserCode() { + return userCode; + } + + /** + * @param userCode the userCode to set + */ + public void setUserCode(String userCode) { + this.userCode = userCode; + } + + /** + * @return the scope + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="device_code_scope", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="scope") + public Set getScope() { + return scope; + } + + /** + * @param scope the scope to set + */ + public void setScope(Set scope) { + this.scope = scope; + } + + @Basic + @Temporal(javax.persistence.TemporalType.TIMESTAMP) + @Column(name = "expiration") + public Date getExpiration() { + return expiration; + } + + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + /** + * @return the clientId + */ + @Basic + @Column(name = "client_id") + public String getClientId() { + return clientId; + } + + /** + * @param clientId the clientId to set + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * @return the params + */ + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="device_code_request_parameter", + joinColumns=@JoinColumn(name="owner_id") + ) + @Column(name="val") + @MapKeyColumn(name="param") + public Map getRequestParameters() { + return requestParameters; + } + + /** + * @param params the params to set + */ + public void setRequestParameters(Map params) { + this.requestParameters = params; + } + + /** + * @return the approved + */ + @Basic + @Column(name = "approved") + public boolean isApproved() { + return approved; + } + + /** + * @param approved the approved to set + */ + public void setApproved(boolean approved) { + this.approved = approved; + } + + /** + * The authentication in place when this token was created. + * @return the authentication + */ + @ManyToOne + @JoinColumn(name = "auth_holder_id") + public AuthenticationHolderEntity getAuthenticationHolder() { + return authenticationHolder; + } + + /** + * @param authentication the authentication to set + */ + public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHolder) { + this.authenticationHolder = authenticationHolder; + } + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java index 39b4204101..d1bda807b7 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2AccessTokenEntity.java @@ -1,25 +1,25 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; -import java.text.ParseException; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -29,6 +29,7 @@ import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -36,19 +37,26 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; -import javax.persistence.OneToOne; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.Transient; +import org.mitre.oauth2.model.convert.JWTStringConverter; +import org.mitre.openid.connect.model.ApprovedSite; +import org.mitre.uma.model.Permission; import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Deserializer; +import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson1Serializer; +import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Deserializer; +import org.springframework.security.oauth2.common.OAuth2AccessTokenJackson2Serializer; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTParser; /** * @author jricher @@ -57,18 +65,39 @@ @Entity @Table(name = "access_token") @NamedQueries({ - @NamedQuery(name = "OAuth2AccessTokenEntity.getAll", query = "select a from OAuth2AccessTokenEntity a"), - @NamedQuery(name = "OAuth2AccessTokenEntity.getByRefreshToken", query = "select a from OAuth2AccessTokenEntity a where a.refreshToken = :refreshToken"), - @NamedQuery(name = "OAuth2AccessTokenEntity.getByClient", query = "select a from OAuth2AccessTokenEntity a where a.client = :client"), - @NamedQuery(name = "OAuth2AccessTokenEntity.getByAuthentication", query = "select a from OAuth2AccessTokenEntity a where a.authenticationHolder.authentication = :authentication"), - @NamedQuery(name = "OAuth2AccessTokenEntity.getByIdToken", query = "select a from OAuth2AccessTokenEntity a where a.idToken = :idToken"), - @NamedQuery(name = "OAuth2AccessTokenEntity.getByTokenValue", query = "select a from OAuth2AccessTokenEntity a where a.value = :tokenValue") + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_ALL, query = "select a from OAuth2AccessTokenEntity a"), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_EXPIRED_BY_DATE, query = "select a from OAuth2AccessTokenEntity a where a.expiration <= :" + OAuth2AccessTokenEntity.PARAM_DATE), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_REFRESH_TOKEN, query = "select a from OAuth2AccessTokenEntity a where a.refreshToken = :" + OAuth2AccessTokenEntity.PARAM_REFERSH_TOKEN), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_CLIENT, query = "select a from OAuth2AccessTokenEntity a where a.client = :" + OAuth2AccessTokenEntity.PARAM_CLIENT), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_TOKEN_VALUE, query = "select a from OAuth2AccessTokenEntity a where a.jwt = :" + OAuth2AccessTokenEntity.PARAM_TOKEN_VALUE), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_APPROVED_SITE, query = "select a from OAuth2AccessTokenEntity a where a.approvedSite = :" + OAuth2AccessTokenEntity.PARAM_APPROVED_SITE), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_RESOURCE_SET, query = "select a from OAuth2AccessTokenEntity a join a.permissions p where p.resourceSet.id = :" + OAuth2AccessTokenEntity.PARAM_RESOURCE_SET_ID), + @NamedQuery(name = OAuth2AccessTokenEntity.QUERY_BY_NAME, query = "select r from OAuth2AccessTokenEntity r where r.authenticationHolder.userAuth.name = :" + OAuth2AccessTokenEntity.PARAM_NAME) }) -//@JsonSerialize(using = OAuth2AccessTokenSerializer.class) -//@JsonDeserialize(using = OAuth2AccessTokenDeserializer.class) +@org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class) +@org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class) +@com.fasterxml.jackson.databind.annotation.JsonSerialize(using = OAuth2AccessTokenJackson2Serializer.class) +@com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) public class OAuth2AccessTokenEntity implements OAuth2AccessToken { - public static String ID_TOKEN_FIELD_NAME = "id_token"; + public static final String QUERY_BY_APPROVED_SITE = "OAuth2AccessTokenEntity.getByApprovedSite"; + public static final String QUERY_BY_TOKEN_VALUE = "OAuth2AccessTokenEntity.getByTokenValue"; + public static final String QUERY_BY_CLIENT = "OAuth2AccessTokenEntity.getByClient"; + public static final String QUERY_BY_REFRESH_TOKEN = "OAuth2AccessTokenEntity.getByRefreshToken"; + public static final String QUERY_EXPIRED_BY_DATE = "OAuth2AccessTokenEntity.getAllExpiredByDate"; + public static final String QUERY_ALL = "OAuth2AccessTokenEntity.getAll"; + public static final String QUERY_BY_RESOURCE_SET = "OAuth2AccessTokenEntity.getByResourceSet"; + public static final String QUERY_BY_NAME = "OAuth2AccessTokenEntity.getByName"; + + public static final String PARAM_TOKEN_VALUE = "tokenValue"; + public static final String PARAM_CLIENT = "client"; + public static final String PARAM_REFERSH_TOKEN = "refreshToken"; + public static final String PARAM_DATE = "date"; + public static final String PARAM_RESOURCE_SET_ID = "rsid"; + public static final String PARAM_APPROVED_SITE = "approvedSite"; + public static final String PARAM_NAME = "name"; + + public static final String ID_TOKEN_FIELD_NAME = "id_token"; private Long id; @@ -78,8 +107,6 @@ public class OAuth2AccessTokenEntity implements OAuth2AccessToken { private JWT jwtValue; // JWT-encoded access token value - private OAuth2AccessTokenEntity idToken; // JWT-encoded OpenID Connect IdToken - private Date expiration; private String tokenType = OAuth2AccessToken.BEARER_TYPE; @@ -88,6 +115,12 @@ public class OAuth2AccessTokenEntity implements OAuth2AccessToken { private Set scope; + private Set permissions; + + private ApprovedSite approvedSite; + + private Map additionalInformation = new HashMap<>(); // ephemeral map of items to be added to the OAuth token response + /** * Create a new, blank access token */ @@ -113,16 +146,13 @@ public void setId(Long id) { } /** - * Get all additional information to be sent to the serializer. Inserts a copy of the IdToken (in JWT String form). + * Get all additional information to be sent to the serializer as part of the token response. + * This map is not persisted to the database. */ @Override @Transient public Map getAdditionalInformation() { - Map map = new HashMap(); //super.getAdditionalInformation(); - if (getIdToken() != null) { - map.put(ID_TOKEN_FIELD_NAME, getIdTokenString()); - } - return map; + return additionalInformation; } /** @@ -162,22 +192,11 @@ public void setClient(ClientDetailsEntity client) { * Get the string-encoded value of this access token. */ @Override - @Basic - @Column(name="token_value") + @Transient public String getValue() { return jwtValue.serialize(); } - /** - * Set the "value" of this Access Token - * - * @param value the JWT string - * @throws ParseException if "value" is not a properly formatted JWT string - */ - public void setValue(String value) throws ParseException { - setJwt(JWTParser.parse(value)); - } - @Override @Basic @Temporal(javax.persistence.TemporalType.TIMESTAMP) @@ -240,38 +259,12 @@ public boolean isExpired() { return getExpiration() == null ? false : System.currentTimeMillis() > getExpiration().getTime(); } - /** - * @return the idToken - */ - @OneToOne(cascade=CascadeType.ALL) // one-to-one mapping for now - @JoinColumn(name = "id_token_id") - public OAuth2AccessTokenEntity getIdToken() { - return idToken; - } - - /** - * @param idToken the idToken to set - */ - public void setIdToken(OAuth2AccessTokenEntity idToken) { - this.idToken = idToken; - } - - /** - * @return the idTokenString - */ - @Transient - public String getIdTokenString() { - if (idToken != null) { - return idToken.getValue(); // get the JWT string value of the id token entity - } else { - return null; - } - } - /** * @return the jwtValue */ - @Transient + @Basic + @Column(name="token_value") + @Convert(converter = JWTStringConverter.class) public JWT getJwt() { return jwtValue; } @@ -299,4 +292,44 @@ public int getExpiresIn() { } } + /** + * @return the permissions + */ + @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinTable( + name = "access_token_permissions", + joinColumns = @JoinColumn(name = "access_token_id"), + inverseJoinColumns = @JoinColumn(name = "permission_id") + ) + public Set getPermissions() { + return permissions; + } + + /** + * @param permissions the permissions to set + */ + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + + @ManyToOne + @JoinColumn(name="approved_site_id") + public ApprovedSite getApprovedSite() { + return approvedSite; + } + + public void setApprovedSite(ApprovedSite approvedSite) { + this.approvedSite = approvedSite; + } + + /** + * Add the ID Token to the additionalInformation map for a token response. + * @param idToken + */ + @Transient + public void setIdToken(JWT idToken) { + if (idToken != null) { + additionalInformation.put(ID_TOKEN_FIELD_NAME, idToken.serialize()); + } + } } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java index 6a70da7acd..f6c2d2153c 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/OAuth2RefreshTokenEntity.java @@ -1,29 +1,30 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; -import java.text.ParseException; import java.util.Date; import javax.persistence.Basic; import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; @@ -37,10 +38,10 @@ import javax.persistence.Temporal; import javax.persistence.Transient; +import org.mitre.oauth2.model.convert.JWTStringConverter; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTParser; /** * @author jricher @@ -49,13 +50,25 @@ @Entity @Table(name = "refresh_token") @NamedQueries({ - @NamedQuery(name = "OAuth2RefreshTokenEntity.getAll", query = "select r from OAuth2RefreshTokenEntity r"), - @NamedQuery(name = "OAuth2RefreshTokenEntity.getByClient", query = "select r from OAuth2RefreshTokenEntity r where r.client = :client"), - @NamedQuery(name = "OAuth2RefreshTokenEntity.getByTokenValue", query = "select r from OAuth2RefreshTokenEntity r where r.value = :tokenValue"), - @NamedQuery(name = "OAuth2RefreshTokenEntity.getByAuthentication", query = "select r from OAuth2RefreshTokenEntity r where r.authenticationHolder.authentication = :authentication") + @NamedQuery(name = OAuth2RefreshTokenEntity.QUERY_ALL, query = "select r from OAuth2RefreshTokenEntity r"), + @NamedQuery(name = OAuth2RefreshTokenEntity.QUERY_EXPIRED_BY_DATE, query = "select r from OAuth2RefreshTokenEntity r where r.expiration <= :" + OAuth2RefreshTokenEntity.PARAM_DATE), + @NamedQuery(name = OAuth2RefreshTokenEntity.QUERY_BY_CLIENT, query = "select r from OAuth2RefreshTokenEntity r where r.client = :" + OAuth2RefreshTokenEntity.PARAM_CLIENT), + @NamedQuery(name = OAuth2RefreshTokenEntity.QUERY_BY_TOKEN_VALUE, query = "select r from OAuth2RefreshTokenEntity r where r.jwt = :" + OAuth2RefreshTokenEntity.PARAM_TOKEN_VALUE), + @NamedQuery(name = OAuth2RefreshTokenEntity.QUERY_BY_NAME, query = "select r from OAuth2RefreshTokenEntity r where r.authenticationHolder.userAuth.name = :" + OAuth2RefreshTokenEntity.PARAM_NAME) }) public class OAuth2RefreshTokenEntity implements OAuth2RefreshToken { + public static final String QUERY_BY_TOKEN_VALUE = "OAuth2RefreshTokenEntity.getByTokenValue"; + public static final String QUERY_BY_CLIENT = "OAuth2RefreshTokenEntity.getByClient"; + public static final String QUERY_EXPIRED_BY_DATE = "OAuth2RefreshTokenEntity.getAllExpiredByDate"; + public static final String QUERY_ALL = "OAuth2RefreshTokenEntity.getAll"; + public static final String QUERY_BY_NAME = "OAuth2RefreshTokenEntity.getByName"; + + public static final String PARAM_TOKEN_VALUE = "tokenValue"; + public static final String PARAM_CLIENT = "client"; + public static final String PARAM_DATE = "date"; + public static final String PARAM_NAME = "name"; + private Long id; private AuthenticationHolderEntity authenticationHolder; @@ -69,7 +82,7 @@ public class OAuth2RefreshTokenEntity implements OAuth2RefreshToken { private Date expiration; /** - * + * */ public OAuth2RefreshTokenEntity() { @@ -95,7 +108,7 @@ public void setId(Long id) { /** * The authentication in place when the original access token was * created - * + * * @return the authentication */ @ManyToOne @@ -115,21 +128,11 @@ public void setAuthenticationHolder(AuthenticationHolderEntity authenticationHol * Get the JWT-encoded value of this token */ @Override - @Basic - @Column(name="token_value") + @Transient public String getValue() { return jwt.serialize(); } - /** - * Set the value of this token as a string. Parses the string into a JWT. - * @param value - * @throws ParseException if the value is not a valid JWT string - */ - public void setValue(String value) throws ParseException { - setJwt(JWTParser.parse(value)); - } - @Basic @Temporal(javax.persistence.TemporalType.TIMESTAMP) @Column(name = "expiration") @@ -174,7 +177,9 @@ public void setClient(ClientDetailsEntity client) { * Get the JWT object directly * @return the jwt */ - @Transient + @Basic + @Column(name="token_value") + @Convert(converter = JWTStringConverter.class) public JWT getJwt() { return jwt; } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/PKCEAlgorithm.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/PKCEAlgorithm.java new file mode 100644 index 0000000000..5b5d5a5478 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/PKCEAlgorithm.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model; + +import com.nimbusds.jose.Algorithm; +import com.nimbusds.jose.Requirement; + +/** + * @author jricher + * + */ +public final class PKCEAlgorithm extends Algorithm { + + /** + * + */ + private static final long serialVersionUID = 7752852583210088925L; + + public static final PKCEAlgorithm plain = new PKCEAlgorithm("plain", Requirement.REQUIRED); + + public static final PKCEAlgorithm S256 = new PKCEAlgorithm("S256", Requirement.OPTIONAL); + + public PKCEAlgorithm(String name, Requirement req) { + super(name, req); + } + + public PKCEAlgorithm(String name) { + super(name, null); + } + + public static PKCEAlgorithm parse(final String s) { + if (s.equals(plain.getName())) { + return plain; + } else if (s.equals(S256.getName())) { + return S256; + } else { + return new PKCEAlgorithm(s); + } + } + + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClient.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClient.java index 50a2cd3d1a..6e4003937f 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClient.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClient.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -23,17 +24,17 @@ import java.util.Map; import java.util.Set; -import org.mitre.jose.JWEAlgorithmEmbed; -import org.mitre.jose.JWEEncryptionMethodEmbed; -import org.mitre.jose.JWSAlgorithmEmbed; import org.mitre.oauth2.model.ClientDetailsEntity.AppType; import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType; import org.springframework.security.core.GrantedAuthority; +import com.google.gson.JsonObject; import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; /** * @author jricher @@ -47,9 +48,10 @@ public class RegisteredClient { private Date clientSecretExpiresAt; private Date clientIdIssuedAt; private ClientDetailsEntity client; + private JsonObject src; /** - * + * */ public RegisteredClient() { this.client = new ClientDetailsEntity(); @@ -463,6 +465,22 @@ public String getJwksUri() { public void setJwksUri(String jwksUri) { client.setJwksUri(jwksUri); } + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getJwks() + */ + public JWKSet getJwks() { + return client.getJwks(); + } + + /** + * @param jwks + * @see org.mitre.oauth2.model.ClientDetailsEntity#setJwks(com.nimbusds.jose.jwk.JWKSet) + */ + public void setJwks(JWKSet jwks) { + client.setJwks(jwks); + } + /** * @return * @see org.mitre.oauth2.model.ClientDetailsEntity#getSectorIdentifierUri() @@ -549,17 +567,17 @@ public void setInitiateLoginUri(String initiateLoginUri) { } /** * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getPostLogoutRedirectUri() + * @see org.mitre.oauth2.model.ClientDetailsEntity#getPostLogoutRedirectUris() */ - public String getPostLogoutRedirectUri() { - return client.getPostLogoutRedirectUri(); + public Set getPostLogoutRedirectUris() { + return client.getPostLogoutRedirectUris(); } /** * @param postLogoutRedirectUri - * @see org.mitre.oauth2.model.ClientDetailsEntity#setPostLogoutRedirectUri(java.lang.String) + * @see org.mitre.oauth2.model.ClientDetailsEntity#setPostLogoutRedirectUris(java.lang.String) */ - public void setPostLogoutRedirectUri(String postLogoutRedirectUri) { - client.setPostLogoutRedirectUri(postLogoutRedirectUri); + public void setPostLogoutRedirectUris(Set postLogoutRedirectUri) { + client.setPostLogoutRedirectUris(postLogoutRedirectUri); } /** * @return @@ -575,117 +593,6 @@ public Set getRequestUris() { public void setRequestUris(Set requestUris) { client.setRequestUris(requestUris); } - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getRequestObjectSigningAlgEmbed() - */ - public JWSAlgorithmEmbed getRequestObjectSigningAlgEmbed() { - return client.getRequestObjectSigningAlgEmbed(); - } - - /** - * @param requestObjectSigningAlg - * @see org.mitre.oauth2.model.ClientDetailsEntity#setRequestObjectSigningAlgEmbed(org.mitre.jose.JWSAlgorithmEmbed) - */ - public void setRequestObjectSigningAlgEmbed(JWSAlgorithmEmbed requestObjectSigningAlg) { - client.setRequestObjectSigningAlgEmbed(requestObjectSigningAlg); - } - - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getUserInfoSignedResponseAlgEmbed() - */ - public JWSAlgorithmEmbed getUserInfoSignedResponseAlgEmbed() { - return client.getUserInfoSignedResponseAlgEmbed(); - } - - /** - * @param userInfoSignedResponseAlg - * @see org.mitre.oauth2.model.ClientDetailsEntity#setUserInfoSignedResponseAlgEmbed(org.mitre.jose.JWSAlgorithmEmbed) - */ - public void setUserInfoSignedResponseAlgEmbed(JWSAlgorithmEmbed userInfoSignedResponseAlg) { - client.setUserInfoSignedResponseAlgEmbed(userInfoSignedResponseAlg); - } - - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getUserInfoEncryptedResponseAlgEmbed() - */ - public JWEAlgorithmEmbed getUserInfoEncryptedResponseAlgEmbed() { - return client.getUserInfoEncryptedResponseAlgEmbed(); - } - - /** - * @param userInfoEncryptedResponseAlg - * @see org.mitre.oauth2.model.ClientDetailsEntity#setUserInfoEncryptedResponseAlgEmbed(org.mitre.jose.JWEAlgorithmEmbed) - */ - public void setUserInfoEncryptedResponseAlgEmbed(JWEAlgorithmEmbed userInfoEncryptedResponseAlg) { - client.setUserInfoEncryptedResponseAlgEmbed(userInfoEncryptedResponseAlg); - } - - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getUserInfoEncryptedResponseEncEmbed() - */ - public JWEEncryptionMethodEmbed getUserInfoEncryptedResponseEncEmbed() { - return client.getUserInfoEncryptedResponseEncEmbed(); - } - - /** - * @param userInfoEncryptedResponseEnc - * @see org.mitre.oauth2.model.ClientDetailsEntity#setUserInfoEncryptedResponseEncEmbed(org.mitre.jose.JWEEncryptionMethodEmbed) - */ - public void setUserInfoEncryptedResponseEncEmbed(JWEEncryptionMethodEmbed userInfoEncryptedResponseEnc) { - client.setUserInfoEncryptedResponseEncEmbed(userInfoEncryptedResponseEnc); - } - - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getIdTokenSignedResponseAlgEmbed() - */ - public JWSAlgorithmEmbed getIdTokenSignedResponseAlgEmbed() { - return client.getIdTokenSignedResponseAlgEmbed(); - } - - /** - * @param idTokenSignedResponseAlg - * @see org.mitre.oauth2.model.ClientDetailsEntity#setIdTokenSignedResponseAlgEmbed(org.mitre.jose.JWSAlgorithmEmbed) - */ - public void setIdTokenSignedResponseAlgEmbed(JWSAlgorithmEmbed idTokenSignedResponseAlg) { - client.setIdTokenSignedResponseAlgEmbed(idTokenSignedResponseAlg); - } - - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getIdTokenEncryptedResponseAlgEmbed() - */ - public JWEAlgorithmEmbed getIdTokenEncryptedResponseAlgEmbed() { - return client.getIdTokenEncryptedResponseAlgEmbed(); - } - - /** - * @param idTokenEncryptedResponseAlg - * @see org.mitre.oauth2.model.ClientDetailsEntity#setIdTokenEncryptedResponseAlgEmbed(org.mitre.jose.JWEAlgorithmEmbed) - */ - public void setIdTokenEncryptedResponseAlgEmbed(JWEAlgorithmEmbed idTokenEncryptedResponseAlg) { - client.setIdTokenEncryptedResponseAlgEmbed(idTokenEncryptedResponseAlg); - } - - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getIdTokenEncryptedResponseEncEmbed() - */ - public JWEEncryptionMethodEmbed getIdTokenEncryptedResponseEncEmbed() { - return client.getIdTokenEncryptedResponseEncEmbed(); - } - - /** - * @param idTokenEncryptedResponseEnc - * @see org.mitre.oauth2.model.ClientDetailsEntity#setIdTokenEncryptedResponseEncEmbed(org.mitre.jose.JWEEncryptionMethodEmbed) - */ - public void setIdTokenEncryptedResponseEncEmbed(JWEEncryptionMethodEmbed idTokenEncryptedResponseEnc) { - client.setIdTokenEncryptedResponseEncEmbed(idTokenEncryptedResponseEnc); - } /** * @return @@ -799,22 +706,6 @@ public void setIdTokenEncryptedResponseEnc(EncryptionMethod idTokenEncryptedResp client.setIdTokenEncryptedResponseEnc(idTokenEncryptedResponseEnc); } - /** - * @return - * @see org.mitre.oauth2.model.ClientDetailsEntity#getTokenEndpointAuthSigningAlgEmbed() - */ - public JWSAlgorithmEmbed getTokenEndpointAuthSigningAlgEmbed() { - return client.getTokenEndpointAuthSigningAlgEmbed(); - } - - /** - * @param tokenEndpointAuthSigningAlgEmbed - * @see org.mitre.oauth2.model.ClientDetailsEntity#setTokenEndpointAuthSigningAlgEmbed(org.mitre.jose.JWSAlgorithmEmbed) - */ - public void setTokenEndpointAuthSigningAlgEmbed(JWSAlgorithmEmbed tokenEndpointAuthSigningAlgEmbed) { - client.setTokenEndpointAuthSigningAlgEmbed(tokenEndpointAuthSigningAlgEmbed); - } - /** * @return * @see org.mitre.oauth2.model.ClientDetailsEntity#getTokenEndpointAuthSigningAlg() @@ -894,6 +785,116 @@ public void setClientIdIssuedAt(Date issuedAt) { this.clientIdIssuedAt = issuedAt; } + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getClaimsRedirectUris() + */ + public Set getClaimsRedirectUris() { + return client.getClaimsRedirectUris(); + } + + /** + * @param claimsRedirectUris + * @see org.mitre.oauth2.model.ClientDetailsEntity#setClaimsRedirectUris(java.util.Set) + */ + public void setClaimsRedirectUris(Set claimsRedirectUris) { + client.setClaimsRedirectUris(claimsRedirectUris); + } + + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getSoftwareStatement() + */ + public JWT getSoftwareStatement() { + return client.getSoftwareStatement(); + } + + /** + * @param softwareStatement + * @see org.mitre.oauth2.model.ClientDetailsEntity#setSoftwareStatement(com.nimbusds.jwt.JWT) + */ + public void setSoftwareStatement(JWT softwareStatement) { + client.setSoftwareStatement(softwareStatement); + } + + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getCodeChallengeMethod() + */ + public PKCEAlgorithm getCodeChallengeMethod() { + return client.getCodeChallengeMethod(); + } + + /** + * @param codeChallengeMethod + * @see org.mitre.oauth2.model.ClientDetailsEntity#setCodeChallengeMethod(org.mitre.oauth2.model.PKCEAlgorithm) + */ + public void setCodeChallengeMethod(PKCEAlgorithm codeChallengeMethod) { + client.setCodeChallengeMethod(codeChallengeMethod); + } + + /** + * @return the src + */ + public JsonObject getSource() { + return src; + } + + /** + * @param src the src to set + */ + public void setSource(JsonObject src) { + this.src = src; + } + + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getDeviceCodeValiditySeconds() + */ + public Integer getDeviceCodeValiditySeconds() { + return client.getDeviceCodeValiditySeconds(); + } + + /** + * @param deviceCodeValiditySeconds + * @see org.mitre.oauth2.model.ClientDetailsEntity#setDeviceCodeValiditySeconds(java.lang.Integer) + */ + public void setDeviceCodeValiditySeconds(Integer deviceCodeValiditySeconds) { + client.setDeviceCodeValiditySeconds(deviceCodeValiditySeconds); + } + + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getSoftwareId() + */ + public String getSoftwareId() { + return client.getSoftwareId(); + } + + /** + * @param softwareId + * @see org.mitre.oauth2.model.ClientDetailsEntity#setSoftwareId(java.lang.String) + */ + public void setSoftwareId(String softwareId) { + client.setSoftwareId(softwareId); + } + + /** + * @return + * @see org.mitre.oauth2.model.ClientDetailsEntity#getSoftwareVersion() + */ + public String getSoftwareVersion() { + return client.getSoftwareVersion(); + } + + /** + * @param softwareVersion + * @see org.mitre.oauth2.model.ClientDetailsEntity#setSoftwareVersion(java.lang.String) + */ + public void setSoftwareVersion(String softwareVersion) { + client.setSoftwareVersion(softwareVersion); + } + } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java new file mode 100644 index 0000000000..79231b5238 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/RegisteredClientFields.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.oauth2.model; + +public interface RegisteredClientFields { + public String SOFTWARE_ID = "software_id"; + public String SOFTWARE_VERSION = "software_version"; + public String SOFTWARE_STATEMENT = "software_statement"; + public String CLAIMS_REDIRECT_URIS = "claims_redirect_uris"; + public String CLIENT_SECRET_EXPIRES_AT = "client_secret_expires_at"; + public String CLIENT_ID_ISSUED_AT = "client_id_issued_at"; + public String REGISTRATION_CLIENT_URI = "registration_client_uri"; + public String REGISTRATION_ACCESS_TOKEN = "registration_access_token"; + public String REQUEST_URIS = "request_uris"; + public String POST_LOGOUT_REDIRECT_URIS = "post_logout_redirect_uris"; + public String INITIATE_LOGIN_URI = "initiate_login_uri"; + public String DEFAULT_ACR_VALUES = "default_acr_values"; + public String REQUIRE_AUTH_TIME = "require_auth_time"; + public String DEFAULT_MAX_AGE = "default_max_age"; + public String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "token_endpoint_auth_signing_alg"; + public String ID_TOKEN_ENCRYPTED_RESPONSE_ENC = "id_token_encrypted_response_enc"; + public String ID_TOKEN_ENCRYPTED_RESPONSE_ALG = "id_token_encrypted_response_alg"; + public String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg"; + public String USERINFO_ENCRYPTED_RESPONSE_ENC = "userinfo_encrypted_response_enc"; + public String USERINFO_ENCRYPTED_RESPONSE_ALG = "userinfo_encrypted_response_alg"; + public String USERINFO_SIGNED_RESPONSE_ALG = "userinfo_signed_response_alg"; + public String REQUEST_OBJECT_SIGNING_ALG = "request_object_signing_alg"; + public String SUBJECT_TYPE = "subject_type"; + public String SECTOR_IDENTIFIER_URI = "sector_identifier_uri"; + public String APPLICATION_TYPE = "application_type"; + public String JWKS_URI = "jwks_uri"; + public String JWKS = "jwks"; + public String SCOPE_SEPARATOR = " "; + public String POLICY_URI = "policy_uri"; + public String RESPONSE_TYPES = "response_types"; + public String GRANT_TYPES = "grant_types"; + public String SCOPE = "scope"; + public String TOKEN_ENDPOINT_AUTH_METHOD = "token_endpoint_auth_method"; + public String TOS_URI = "tos_uri"; + public String CONTACTS = "contacts"; + public String LOGO_URI = "logo_uri"; + public String CLIENT_URI = "client_uri"; + public String CLIENT_NAME = "client_name"; + public String REDIRECT_URIS = "redirect_uris"; + public String CLIENT_SECRET = "client_secret"; + public String CLIENT_ID = "client_id"; + public String CODE_CHALLENGE_METHOD = "code_challenge_method"; +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/SavedUserAuthentication.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/SavedUserAuthentication.java new file mode 100644 index 0000000000..21fa34a830 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/SavedUserAuthentication.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model; + +import java.util.Collection; +import java.util.HashSet; + +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; +import javax.persistence.Transient; + +import org.mitre.oauth2.model.convert.SimpleGrantedAuthorityStringConverter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +/** + * This class stands in for an original Authentication object. + * + * @author jricher + * + */ +@Entity +@Table(name="saved_user_auth") +public class SavedUserAuthentication implements Authentication { + + private static final long serialVersionUID = -1804249963940323488L; + + private Long id; + + private String name; + + private Collection authorities; + + private boolean authenticated; + + private String sourceClass; + + /** + * Create a Saved Auth from an existing Auth token + */ + public SavedUserAuthentication(Authentication src) { + setName(src.getName()); + setAuthorities(new HashSet<>(src.getAuthorities())); + setAuthenticated(src.isAuthenticated()); + + if (src instanceof SavedUserAuthentication) { + // if we're copying in a saved auth, carry over the original class name + setSourceClass(((SavedUserAuthentication) src).getSourceClass()); + } else { + setSourceClass(src.getClass().getName()); + } + } + + /** + * Create an empty saved auth + */ + public SavedUserAuthentication() { + + } + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + @Override + @Basic + @Column(name="name") + public String getName() { + return name; + } + + @Override + @ElementCollection(fetch = FetchType.EAGER) + @CollectionTable( + name="saved_user_auth_authority", + joinColumns=@JoinColumn(name="owner_id") + ) + @Convert(converter = SimpleGrantedAuthorityStringConverter.class) + @Column(name="authority") + public Collection getAuthorities() { + return authorities; + } + + @Override + @Transient + public Object getCredentials() { + return ""; + } + + @Override + @Transient + public Object getDetails() { + return null; + } + + @Override + @Transient + public Object getPrincipal() { + return getName(); + } + + @Override + @Basic + @Column(name="authenticated") + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + this.authenticated = isAuthenticated; + } + + /** + * @return the sourceClass + */ + @Basic + @Column(name="source_class") + public String getSourceClass() { + return sourceClass; + } + + /** + * @param sourceClass the sourceClass to set + */ + public void setSourceClass(String sourceClass) { + this.sourceClass = sourceClass; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @param authorities the authorities to set + */ + public void setAuthorities(Collection authorities) { + this.authorities = authorities; + } + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java index ff30857a31..0807b160e8 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/SystemScope.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -28,7 +29,6 @@ import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; -import javax.persistence.Transient; /** * @author jricher @@ -37,20 +37,22 @@ @Entity @Table(name = "system_scope") @NamedQueries({ - @NamedQuery(name = "SystemScope.findAll", query = "select s from SystemScope s ORDER BY s.id"), - @NamedQuery(name = "SystemScope.getByValue", query = "select s from SystemScope s WHERE s.value = :value") + @NamedQuery(name = SystemScope.QUERY_ALL, query = "select s from SystemScope s ORDER BY s.id"), + @NamedQuery(name = SystemScope.QUERY_BY_VALUE, query = "select s from SystemScope s WHERE s.value = :" + SystemScope.PARAM_VALUE) }) public class SystemScope { + public static final String QUERY_BY_VALUE = "SystemScope.getByValue"; + public static final String QUERY_ALL = "SystemScope.findAll"; + + public static final String PARAM_VALUE = "value"; + private Long id; private String value; // scope value private String description; // human-readable description private String icon; // class of the icon to display on the auth page - private boolean allowDynReg = false; // can a dynamically registered client ask for this scope? private boolean defaultScope = false; // is this a default scope for newly-registered clients? - private boolean structured = false; // is this a default scope for newly-registered clients? - private String structuredParamDescription; - private String structuredValue; + private boolean restricted = false; // is this scope restricted to admin-only registration access? /** * Make a blank system scope with no value @@ -124,20 +126,6 @@ public String getIcon() { public void setIcon(String icon) { this.icon = icon; } - /** - * @return the allowDynReg - */ - @Basic - @Column(name = "allow_dyn_reg") - public boolean isAllowDynReg() { - return allowDynReg; - } - /** - * @param allowDynReg the allowDynReg to set - */ - public void setAllowDynReg(boolean allowDynReg) { - this.allowDynReg = allowDynReg; - } /** * @return the defaultScope @@ -156,51 +144,21 @@ public void setDefaultScope(boolean defaultScope) { } /** - * @return the isStructured status - */ - @Basic - @Column(name = "structured") - public boolean isStructured() { - return structured; - } - - /** - * @param structured the structured to set + * @return the restricted */ - public void setStructured(boolean structured) { - this.structured = structured; - } - @Basic - @Column(name = "structured_param_description") - public String getStructuredParamDescription() { - return structuredParamDescription; + @Column(name = "restricted") + public boolean isRestricted() { + return restricted; } /** - * @param isStructured the isStructured to set + * @param restricted the restricted to set */ - public void setStructuredParamDescription(String d) { - this.structuredParamDescription = d; + public void setRestricted(boolean restricted) { + this.restricted = restricted; } - - /** - * @return the structuredValue - */ - @Transient // we don't save the value of a system scope separately - public String getStructuredValue() { - return structuredValue; - } - - /** - * @param structuredValue the structuredValue to set - */ - public void setStructuredValue(String structuredValue) { - this.structuredValue = structuredValue; - } - - /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @@ -208,14 +166,12 @@ public void setStructuredValue(String structuredValue) { public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + (allowDynReg ? 1231 : 1237); result = prime * result + (defaultScope ? 1231 : 1237); - result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + + ((description == null) ? 0 : description.hashCode()); result = prime * result + ((icon == null) ? 0 : icon.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + (structured ? 1231 : 1237); - result = prime * result + ((structuredParamDescription == null) ? 0 : structuredParamDescription.hashCode()); - result = prime * result + ((structuredValue == null) ? 0 : structuredValue.hashCode()); + result = prime * result + (restricted ? 1231 : 1237); result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } @@ -231,13 +187,10 @@ public boolean equals(Object obj) { if (obj == null) { return false; } - if (!(obj instanceof SystemScope)) { + if (getClass() != obj.getClass()) { return false; } SystemScope other = (SystemScope) obj; - if (allowDynReg != other.allowDynReg) { - return false; - } if (defaultScope != other.defaultScope) { return false; } @@ -262,21 +215,7 @@ public boolean equals(Object obj) { } else if (!id.equals(other.id)) { return false; } - if (structured != other.structured) { - return false; - } - if (structuredParamDescription == null) { - if (other.structuredParamDescription != null) { - return false; - } - } else if (!structuredParamDescription.equals(other.structuredParamDescription)) { - return false; - } - if (structuredValue == null) { - if (other.structuredValue != null) { - return false; - } - } else if (!structuredValue.equals(other.structuredValue)) { + if (restricted != other.restricted) { return false; } if (value == null) { @@ -294,8 +233,9 @@ public boolean equals(Object obj) { */ @Override public String toString() { - return "SystemScope [id=" + id + ", value=" + value + ", description=" + description + ", icon=" + icon + ", allowDynReg=" + allowDynReg + ", defaultScope=" + defaultScope + ", structured=" + structured + ", structuredParamDescription=" + structuredParamDescription + ", structuredValue=" - + structuredValue + "]"; + return "SystemScope [id=" + id + ", value=" + value + ", description=" + + description + ", icon=" + icon + ", defaultScope=" + + defaultScope + ", restricted=" + restricted + "]"; } } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWEAlgorithmStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWEAlgorithmStringConverter.java new file mode 100644 index 0000000000..1341cb4bc8 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWEAlgorithmStringConverter.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.nimbusds.jose.JWEAlgorithm; + +@Converter +public class JWEAlgorithmStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(JWEAlgorithm attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public JWEAlgorithm convertToEntityAttribute(String dbData) { + if (dbData != null) { + return JWEAlgorithm.parse(dbData); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWEEncryptionMethodStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWEEncryptionMethodStringConverter.java new file mode 100644 index 0000000000..a9f0355b8b --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWEEncryptionMethodStringConverter.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.nimbusds.jose.EncryptionMethod; + +@Converter +public class JWEEncryptionMethodStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(EncryptionMethod attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public EncryptionMethod convertToEntityAttribute(String dbData) { + if (dbData != null) { + return EncryptionMethod.parse(dbData); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWKSetStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWKSetStringConverter.java new file mode 100644 index 0000000000..f499e1af4b --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWKSetStringConverter.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import java.text.ParseException; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.nimbusds.jose.jwk.JWKSet; + +/** + * @author jricher + * + */ +@Converter +public class JWKSetStringConverter implements AttributeConverter { + + private static Logger logger = LoggerFactory.getLogger(JWKSetStringConverter.class); + + @Override + public String convertToDatabaseColumn(JWKSet attribute) { + if (attribute != null) { + return attribute.toString(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public JWKSet convertToEntityAttribute(String dbData) { + if (dbData != null) { + try { + JWKSet jwks = JWKSet.parse(dbData); + return jwks; + } catch (ParseException e) { + logger.error("Unable to parse JWK Set", e); + return null; + } + } else { + return null; + } + + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWSAlgorithmStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWSAlgorithmStringConverter.java new file mode 100644 index 0000000000..c671c50fa0 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWSAlgorithmStringConverter.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.nimbusds.jose.JWSAlgorithm; + +@Converter +public class JWSAlgorithmStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(JWSAlgorithm attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public JWSAlgorithm convertToEntityAttribute(String dbData) { + if (dbData != null) { + return JWSAlgorithm.parse(dbData); + } else { + return null; + } + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWTStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWTStringConverter.java new file mode 100644 index 0000000000..6f69c6a88b --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JWTStringConverter.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import java.text.ParseException; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +/** + * @author jricher + * + */ +@Converter +public class JWTStringConverter implements AttributeConverter { + + public static Logger logger = LoggerFactory.getLogger(JWTStringConverter.class); + + @Override + public String convertToDatabaseColumn(JWT attribute) { + if (attribute != null) { + return attribute.serialize(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public JWT convertToEntityAttribute(String dbData) { + if (dbData != null) { + try { + JWT jwt = JWTParser.parse(dbData); + return jwt; + } catch (ParseException e) { + logger.error("Unable to parse JWT", e); + return null; + } + } else { + return null; + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JsonElementStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JsonElementStringConverter.java new file mode 100644 index 0000000000..3ee6305372 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/JsonElementStringConverter.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.google.common.base.Strings; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +/** + * @author jricher + * + */ +@Converter +public class JsonElementStringConverter implements AttributeConverter { + + private JsonParser parser = new JsonParser(); + + @Override + public String convertToDatabaseColumn(JsonElement attribute) { + if (attribute != null) { + return attribute.toString(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public JsonElement convertToEntityAttribute(String dbData) { + if (!Strings.isNullOrEmpty(dbData)) { + return parser.parse(dbData); + } else { + return null; + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/PKCEAlgorithmStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/PKCEAlgorithmStringConverter.java new file mode 100644 index 0000000000..4e8359f841 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/PKCEAlgorithmStringConverter.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.mitre.oauth2.model.PKCEAlgorithm; + +/** + * @author jricher + * + */ +@Converter +public class PKCEAlgorithmStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(PKCEAlgorithm attribute) { + if (attribute != null) { + return attribute.getName(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public PKCEAlgorithm convertToEntityAttribute(String dbData) { + if (dbData != null) { + return PKCEAlgorithm.parse(dbData); + } else { + return null; + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/SerializableStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/SerializableStringConverter.java new file mode 100644 index 0000000000..0c3e523884 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/SerializableStringConverter.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import java.io.Serializable; +import java.util.Date; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Translates a Serializable object of certain primitive types + * into a String for storage in the database, for use with the + * OAuth2Request extensions map. + * + * This class does allow some extension data to be lost. + * + * @author jricher + * + */ +@Converter +public class SerializableStringConverter implements AttributeConverter { + + private static Logger logger = LoggerFactory.getLogger(SerializableStringConverter.class); + + @Override + public String convertToDatabaseColumn(Serializable attribute) { + if (attribute == null) { + return null; + } else if (attribute instanceof String) { + return (String) attribute; + } else if (attribute instanceof Long) { + return attribute.toString(); + } else if (attribute instanceof Date) { + return Long.toString(((Date)attribute).getTime()); + } else { + logger.warn("Dropping data from request: " + attribute + " :: " + attribute.getClass()); + return null; + } + } + + @Override + public Serializable convertToEntityAttribute(String dbData) { + return dbData; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java new file mode 100644 index 0000000000..875387508f --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/model/convert/SimpleGrantedAuthorityStringConverter.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * @author jricher + * + */ +@Converter +public class SimpleGrantedAuthorityStringConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(SimpleGrantedAuthority attribute) { + if (attribute != null) { + return attribute.getAuthority(); + } else { + return null; + } + } + + @Override + public SimpleGrantedAuthority convertToEntityAttribute(String dbData) { + if (dbData != null) { + return new SimpleGrantedAuthority(dbData); + } else { + return null; + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java index 8993a3e0c5..1b217de3e2 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthenticationHolderRepository.java @@ -1,34 +1,37 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.repository; +import java.util.List; + +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.AuthenticationHolderEntity; -import org.springframework.security.oauth2.provider.OAuth2Authentication; public interface AuthenticationHolderRepository { + public List getAll(); public AuthenticationHolderEntity getById(Long id); - public AuthenticationHolderEntity getByAuthentication(OAuth2Authentication a); - - public void removeById(Long id); - public void remove(AuthenticationHolderEntity a); public AuthenticationHolderEntity save(AuthenticationHolderEntity a); + public List getOrphanedAuthenticationHolders(); + + public List getOrphanedAuthenticationHolders(PageCriteria pageCriteria); } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java index c35bc9edb9..11375e7e64 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/AuthorizationCodeRepository.java @@ -1,28 +1,30 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.repository; +import java.util.Collection; + +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.AuthorizationCodeEntity; -import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; -import org.springframework.security.oauth2.provider.OAuth2Authentication; /** * Interface for saving and consuming OAuth2 authorization codes as AuthorizationCodeEntitys. - * + * * @author aanganes * */ @@ -30,19 +32,36 @@ public interface AuthorizationCodeRepository { /** * Save an AuthorizationCodeEntity to the repository - * + * * @param authorizationCode the AuthorizationCodeEntity to save * @return the saved AuthorizationCodeEntity */ public AuthorizationCodeEntity save(AuthorizationCodeEntity authorizationCode); /** - * Consume an authorization code. - * + * Get an authorization code from the repository by value. + * * @param code the authorization code value * @return the authentication associated with the code - * @throws InvalidGrantException if no AuthorizationCodeEntity is found with the given value */ - public OAuth2Authentication consume(String code) throws InvalidGrantException; + public AuthorizationCodeEntity getByCode(String code); + + /** + * Remove an authorization code from the repository + * + * @param authorizationCodeEntity + */ + public void remove(AuthorizationCodeEntity authorizationCodeEntity); + + /** + * @return A collection of all expired codes. + */ + public Collection getExpiredCodes(); + + /** + * @return A collection of all expired codes, limited by the given + * PageCriteria. + */ + public Collection getExpiredCodes(PageCriteria pageCriteria); } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java index e5ae81a740..56936ac80a 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2ClientRepository.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.repository; import java.util.Collection; diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java index ffbaf53f44..e71d0a5975 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/OAuth2TokenRepository.java @@ -1,28 +1,31 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.repository; import java.util.List; import java.util.Set; +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.mitre.openid.connect.model.ApprovedSite; +import org.mitre.uma.model.ResourceSet; public interface OAuth2TokenRepository { @@ -49,13 +52,49 @@ public interface OAuth2TokenRepository { public List getAccessTokensForClient(ClientDetailsEntity client); public List getRefreshTokensForClient(ClientDetailsEntity client); - - public OAuth2AccessTokenEntity getByAuthentication(OAuth2Authentication auth); - - public OAuth2AccessTokenEntity getAccessTokenForIdToken(OAuth2AccessTokenEntity idToken); + + public Set getAccessTokensByUserName(String name); + + public Set getRefreshTokensByUserName(String name); public Set getAllAccessTokens(); public Set getAllRefreshTokens(); + public Set getAllExpiredAccessTokens(); + + public Set getAllExpiredAccessTokens(PageCriteria pageCriteria); + + public Set getAllExpiredRefreshTokens(); + + public Set getAllExpiredRefreshTokens(PageCriteria pageCriteria); + + public Set getAccessTokensForResourceSet(ResourceSet rs); + + /** + * removes duplicate access tokens. + * + * @deprecated this method was added to return the remove duplicate access tokens values + * so that {code removeAccessToken(OAuth2AccessTokenEntity o)} would not to fail. the + * removeAccessToken method has been updated so as it will not fail in the event that an + * accessToken has been duplicated, so this method is unnecessary. + * + */ + @Deprecated + public void clearDuplicateAccessTokens(); + + /** + * removes duplicate refresh tokens. + * + * @deprecated this method was added to return the remove duplicate refresh token value + * so that {code removeRefreshToken(OAuth2RefreshTokenEntity o)} would not to fail. the + * removeRefreshToken method has been updated so as it will not fail in the event that + * refreshToken has been duplicated, so this method is unnecessary. + * + */ + @Deprecated + public void clearDuplicateRefreshTokens(); + + public List getAccessTokensForApprovedSite(ApprovedSite approvedSite); + } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java index 1ccdc49645..8c891d566d 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/SystemScopeRepository.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.repository; diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/repository/impl/DeviceCodeRepository.java b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/impl/DeviceCodeRepository.java new file mode 100644 index 0000000000..392932642c --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/repository/impl/DeviceCodeRepository.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.repository.impl; + +import java.util.Collection; + +import org.mitre.oauth2.model.DeviceCode; + +/** + * @author jricher + * + */ +public interface DeviceCodeRepository { + + /** + * @param id + * @return + */ + public DeviceCode getById(Long id); + + /** + * @param deviceCode + * @return + */ + public DeviceCode getByDeviceCode(String deviceCode); + + /** + * @param scope + */ + public void remove(DeviceCode scope); + + /** + * @param scope + * @return + */ + public DeviceCode save(DeviceCode scope); + + /** + * @param userCode + * @return + */ + public DeviceCode getByUserCode(String userCode); + + /** + * @return + */ + public Collection getExpiredCodes(); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java index 691c37bb75..08695c6751 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/ClientDetailsEntityService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.service; import java.util.Collection; diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/DeviceCodeService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/DeviceCodeService.java new file mode 100644 index 0000000000..b9601292ee --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/DeviceCodeService.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.oauth2.service; + +import java.util.Map; +import java.util.Set; + +import org.mitre.oauth2.exception.DeviceCodeCreationException; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.DeviceCode; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Authentication; + +/** + * @author jricher + * + */ +public interface DeviceCodeService { + + /** + * @param userCode + * @return + */ + public DeviceCode lookUpByUserCode(String userCode); + + /** + * @param dc + * @param o2Auth + */ + public DeviceCode approveDeviceCode(DeviceCode dc, OAuth2Authentication o2Auth); + + /** + * @param deviceCode + * @param client + * @return + */ + public DeviceCode findDeviceCode(String deviceCode, ClientDetails client); + + + /** + * + * @param deviceCode + * @param client + */ + public void clearDeviceCode(String deviceCode, ClientDetails client); + + /** + * @param deviceCode + * @param userCode + * @param requestedScopes + * @param client + * @param parameters + * @return + */ + public DeviceCode createNewDeviceCode(Set requestedScopes, ClientDetailsEntity client, Map parameters) throws DeviceCodeCreationException; + + + public void clearExpiredDeviceCodes(); +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java new file mode 100644 index 0000000000..e0250a5035 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/IntrospectionResultAssembler.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.oauth2.service; + +import java.text.SimpleDateFormat; +import java.util.Map; +import java.util.Set; + +import javax.swing.text.DateFormatter; + +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.openid.connect.model.UserInfo; + +/** + * Strategy interface for assembling a token introspection result. + */ +public interface IntrospectionResultAssembler { + + public String TOKEN_TYPE = "token_type"; + public String CLIENT_ID = "client_id"; + public String USER_ID = "user_id"; + public String SUB = "sub"; + public String EXP = "exp"; + public String EXPIRES_AT = "expires_at"; + public String SCOPE_SEPARATOR = " "; + public String SCOPE = "scope"; + public String ACTIVE = "active"; + public DateFormatter dateFormat = new DateFormatter(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")); + + /** + * Assemble a token introspection result from the given access token and user info. + * + * @param accessToken the access token + * @param userInfo the user info + * @param authScopes the scopes the client is authorized for + * @return the token introspection result + */ + Map assembleFrom(OAuth2AccessTokenEntity accessToken, UserInfo userInfo, Set authScopes); + + /** + * Assemble a token introspection result from the given refresh token and user info. + * + * @param refreshToken the refresh token + * @param userInfo the user info + * @param authScopes the scopes the client is authorized for + * @return the token introspection result + */ + Map assembleFrom(OAuth2RefreshTokenEntity refreshToken, UserInfo userInfo, Set authScopes); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java index d9b5b4d6e8..c39ccd90da 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/OAuth2TokenEntityService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.service; import java.util.List; @@ -50,12 +51,6 @@ public interface OAuth2TokenEntityService extends AuthorizationServerTokenServic @Override public OAuth2AccessTokenEntity getAccessToken(OAuth2Authentication authentication); - /** - * @param incomingToken - * @return - */ - public OAuth2AccessTokenEntity getAccessTokenForIdToken(OAuth2AccessTokenEntity idToken); - public OAuth2AccessTokenEntity getAccessTokenById(Long id); public OAuth2RefreshTokenEntity getRefreshTokenById(Long id); @@ -63,4 +58,6 @@ public interface OAuth2TokenEntityService extends AuthorizationServerTokenServic public Set getAllAccessTokensForUser(String name); public Set getAllRefreshTokensForUser(String name); + + public OAuth2AccessTokenEntity getRegistrationAccessTokenForClient(ClientDetailsEntity client); } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java index 4a171c867b..dad93f1711 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/SystemScopeService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.service; @@ -23,6 +24,8 @@ import org.mitre.oauth2.model.SystemScope; +import com.google.common.collect.Sets; + /** * @author jricher * @@ -30,8 +33,17 @@ public interface SystemScopeService { public static final String OFFLINE_ACCESS = "offline_access"; - public static final String ID_TOKEN_SCOPE = "id-token"; - public static final String REGISTRATION_TOKEN_SCOPE = "registration-token"; + public static final String OPENID_SCOPE = "openid"; + public static final String REGISTRATION_TOKEN_SCOPE = "registration-token"; // this scope manages dynamic client registrations + public static final String RESOURCE_TOKEN_SCOPE = "resource-token"; // this scope manages client-style protected resources + public static final String UMA_PROTECTION_SCOPE = "uma_protection"; + public static final String UMA_AUTHORIZATION_SCOPE = "uma_authorization"; + + public static final Set reservedScopes = + Sets.newHashSet( + new SystemScope(REGISTRATION_TOKEN_SCOPE), + new SystemScope(RESOURCE_TOKEN_SCOPE) + ); public Set getAll(); @@ -42,10 +54,25 @@ public interface SystemScopeService { public Set getDefaults(); /** - * Get all scopes that are allowed for dynamic registration on this system + * Get all the reserved system scopes. These can't be used + * by clients directly, but are instead tied to special system + * tokens like id tokens and registration access tokens. + * + * @return + */ + public Set getReserved(); + + /** + * Get all the registered scopes that are restricted. * @return */ - public Set getDynReg(); + public Set getRestricted(); + + /** + * Get all the registered scopes that aren't restricted. + * @return + */ + public Set getUnrestricted(); public SystemScope getById(Long id); @@ -70,19 +97,23 @@ public interface SystemScopeService { public Set toStrings(Set scope); /** - * Test whether the scopes in both sets are compatible, with special - * processing for structured scopes. All scopes in "actual" must exist in - * "expected". If a scope in "expected" is structured and has a value, it - * must be matched exactly by its corresponding scope in "actual". If a - * scope in "expected" is structured but has no value, it may be matched by - * a scope with or without a value in "actual". + * Test whether the scopes in both sets are compatible. All scopes in "actual" must exist in "expected". */ public boolean scopesMatch(Set expected, Set actual); /** - * Remove any system-restricted scopes from the set and return the result. + * Remove any system-reserved or registered restricted scopes from the + * set and return the result. + * @param scopes + * @return + */ + public Set removeRestrictedAndReservedScopes(Set scopes); + + /** + * Remove any system-reserved scopes from the set and return the result. * @param scopes * @return */ - public Set removeRestrictedScopes(Set scopes); + public Set removeReservedScopes(Set scopes); + } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java index 0fd9152094..da7a177c87 100644 --- a/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/DefaultClientUserDetailsService.java @@ -1,24 +1,31 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.oauth2.service.impl; -import java.util.ArrayList; +import java.math.BigInteger; +import java.security.SecureRandom; import java.util.Collection; +import java.util.HashSet; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -26,55 +33,71 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.provider.ClientDetails; -import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.stereotype.Service; +import com.google.common.base.Strings; + /** * Shim layer to convert a ClientDetails service into a UserDetails service - * + * * @author AANGANES * */ @Service("clientUserDetailsService") public class DefaultClientUserDetailsService implements UserDetailsService { + private static GrantedAuthority ROLE_CLIENT = new SimpleGrantedAuthority("ROLE_CLIENT"); + + @Autowired + private ClientDetailsEntityService clientDetailsService; + @Autowired - private ClientDetailsService clientDetailsService; + private ConfigurationPropertiesBean config; @Override public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException { - ClientDetails client = clientDetailsService.loadClientByClientId(clientId); - - if (client != null) { - - String password = client.getClientSecret(); - boolean enabled = true; - boolean accountNonExpired = true; - boolean credentialsNonExpired = true; - boolean accountNonLocked = true; - Collection authorities = client.getAuthorities(); - if (authorities == null || authorities.isEmpty()) { - // automatically inject ROLE_CLIENT if none exists ... - // TODO: this should probably happen on the client service side instead to keep it in the real data model - authorities = new ArrayList(); - GrantedAuthority roleClient = new SimpleGrantedAuthority("ROLE_CLIENT"); - authorities.add(roleClient); - } + try { + ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientId); + + if (client != null) { + + String password = Strings.nullToEmpty(client.getClientSecret()); + + if (config.isHeartMode() || // if we're running HEART mode turn off all client secrets + (client.getTokenEndpointAuthMethod() != null && + (client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) || + client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT)))) { - return new User(clientId, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); - } else { + // Issue a random password each time to prevent password auth from being used (or skipped) + // for private key or shared key clients, see #715 + + password = new BigInteger(512, new SecureRandom()).toString(16); + } + + boolean enabled = true; + boolean accountNonExpired = true; + boolean credentialsNonExpired = true; + boolean accountNonLocked = true; + Collection authorities = new HashSet<>(client.getAuthorities()); + authorities.add(ROLE_CLIENT); + + return new User(clientId, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); + } else { + throw new UsernameNotFoundException("Client not found: " + clientId); + } + } catch (InvalidClientException e) { throw new UsernameNotFoundException("Client not found: " + clientId); } } - public ClientDetailsService getClientDetailsService() { + public ClientDetailsEntityService getClientDetailsService() { return clientDetailsService; } - public void setClientDetailsService(ClientDetailsService clientDetailsService) { + public void setClientDetailsService(ClientDetailsEntityService clientDetailsService) { this.clientDetailsService = clientDetailsService; } diff --git a/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/UriEncodedClientUserDetailsService.java b/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/UriEncodedClientUserDetailsService.java new file mode 100644 index 0000000000..64ef7e45cf --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/oauth2/service/impl/UriEncodedClientUserDetailsService.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.oauth2.service.impl; + +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.HashSet; + +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.stereotype.Service; +import org.springframework.web.util.UriUtils; + +import com.google.common.base.Strings; + +/** + * Loads client details based on URI encoding as passed in from basic auth. + * + * Should only get called if non-encoded provider fails. + * + * @author AANGANES + * + */ +@Service("uriEncodedClientUserDetailsService") +public class UriEncodedClientUserDetailsService implements UserDetailsService { + + private static GrantedAuthority ROLE_CLIENT = new SimpleGrantedAuthority("ROLE_CLIENT"); + + @Autowired + private ClientDetailsEntityService clientDetailsService; + + @Autowired + private ConfigurationPropertiesBean config; + + @Override + public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException { + + try { + String decodedClientId = UriUtils.decode(clientId, "UTF-8"); + + ClientDetailsEntity client = clientDetailsService.loadClientByClientId(decodedClientId); + + if (client != null) { + + String encodedPassword = UriUtils.encodePathSegment(Strings.nullToEmpty(client.getClientSecret()), "UTF-8"); + + if (config.isHeartMode() || // if we're running HEART mode turn off all client secrets + (client.getTokenEndpointAuthMethod() != null && + (client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) || + client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT)))) { + + // Issue a random password each time to prevent password auth from being used (or skipped) + // for private key or shared key clients, see #715 + + encodedPassword = new BigInteger(512, new SecureRandom()).toString(16); + } + + boolean enabled = true; + boolean accountNonExpired = true; + boolean credentialsNonExpired = true; + boolean accountNonLocked = true; + Collection authorities = new HashSet<>(client.getAuthorities()); + authorities.add(ROLE_CLIENT); + + return new User(decodedClientId, encodedPassword, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); + } else { + throw new UsernameNotFoundException("Client not found: " + clientId); + } + } catch (InvalidClientException e) { + throw new UsernameNotFoundException("Client not found: " + clientId); + } + + } + + public ClientDetailsEntityService getClientDetailsService() { + return clientDetailsService; + } + + public void setClientDetailsService(ClientDetailsEntityService clientDetailsService) { + this.clientDetailsService = clientDetailsService; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java index 8ad5f691f4..c07a48da10 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessor.java @@ -1,140 +1,230 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect; -import static org.mitre.discovery.util.JsonUtils.getAsArray; -import static org.mitre.discovery.util.JsonUtils.getAsDate; -import static org.mitre.discovery.util.JsonUtils.getAsJweAlgorithm; -import static org.mitre.discovery.util.JsonUtils.getAsJweEncryptionMethod; -import static org.mitre.discovery.util.JsonUtils.getAsJwsAlgorithm; -import static org.mitre.discovery.util.JsonUtils.getAsString; -import static org.mitre.discovery.util.JsonUtils.getAsStringSet; +import static org.mitre.util.JsonUtils.getAsArray; +import static org.mitre.util.JsonUtils.getAsDate; +import static org.mitre.util.JsonUtils.getAsJweAlgorithm; +import static org.mitre.util.JsonUtils.getAsJweEncryptionMethod; +import static org.mitre.util.JsonUtils.getAsJwsAlgorithm; +import static org.mitre.util.JsonUtils.getAsPkceAlgorithm; +import static org.mitre.util.JsonUtils.getAsString; +import static org.mitre.util.JsonUtils.getAsStringSet; + +import java.text.ParseException; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.ClientDetailsEntity.AppType; import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType; import org.mitre.oauth2.model.RegisteredClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.base.Splitter; +import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE; +import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS; +import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID; +import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT; +import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME; +import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET; +import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT; +import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.CODE_CHALLENGE_METHOD; +import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS; +import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES; +import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE; +import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES; +import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG; +import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC; +import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG; +import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.JWKS; +import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.LOGO_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS; +import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS; +import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN; +import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG; +import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS; +import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME; +import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES; +import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE; +import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE_SEPARATOR; +import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_ID; +import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT; +import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_VERSION; +import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE; +import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD; +import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG; +import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG; +import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC; +import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG; /** + * Utility class to handle the parsing and serialization of ClientDetails objects. + * * @author jricher * */ public class ClientDetailsEntityJsonProcessor { + private static Logger logger = LoggerFactory.getLogger(ClientDetailsEntityJsonProcessor.class); + private static JsonParser parser = new JsonParser(); /** - * + * * Create an unbound ClientDetailsEntity from the given JSON string. - * + * * @param jsonString * @return the entity if successful, null otherwise */ public static ClientDetailsEntity parse(String jsonString) { JsonElement jsonEl = parser.parse(jsonString); + return parse(jsonEl); + } + + public static ClientDetailsEntity parse(JsonElement jsonEl) { if (jsonEl.isJsonObject()) { JsonObject o = jsonEl.getAsJsonObject(); ClientDetailsEntity c = new ClientDetailsEntity(); - // TODO: make these field names into constants - // these two fields should only be sent in the update request, and MUST match existing values - c.setClientId(getAsString(o, "client_id")); - c.setClientSecret(getAsString(o, "client_secret")); + c.setClientId(getAsString(o, CLIENT_ID)); + c.setClientSecret(getAsString(o, CLIENT_SECRET)); // OAuth DynReg - c.setRedirectUris(getAsStringSet(o, "redirect_uris")); - c.setClientName(getAsString(o, "client_name")); - c.setClientUri(getAsString(o, "client_uri")); - c.setLogoUri(getAsString(o, "logo_uri")); - c.setContacts(getAsStringSet(o, "contacts")); - c.setTosUri(getAsString(o, "tos_uri")); - - String authMethod = getAsString(o, "token_endpoint_auth_method"); + c.setRedirectUris(getAsStringSet(o, REDIRECT_URIS)); + c.setClientName(getAsString(o, CLIENT_NAME)); + c.setClientUri(getAsString(o, CLIENT_URI)); + c.setLogoUri(getAsString(o, LOGO_URI)); + c.setContacts(getAsStringSet(o, CONTACTS)); + c.setTosUri(getAsString(o, TOS_URI)); + + String authMethod = getAsString(o, TOKEN_ENDPOINT_AUTH_METHOD); if (authMethod != null) { c.setTokenEndpointAuthMethod(AuthMethod.getByValue(authMethod)); } // scope is a space-separated string - String scope = getAsString(o, "scope"); + String scope = getAsString(o, SCOPE); if (scope != null) { - c.setScope(Sets.newHashSet(Splitter.on(" ").split(scope))); + c.setScope(Sets.newHashSet(Splitter.on(SCOPE_SEPARATOR).split(scope))); } - c.setGrantTypes(getAsStringSet(o, "grant_types")); - c.setResponseTypes(getAsStringSet(o, "response_types")); - c.setPolicyUri(getAsString(o, "policy_uri")); - c.setJwksUri(getAsString(o, "jwks_uri")); - + c.setGrantTypes(getAsStringSet(o, GRANT_TYPES)); + c.setResponseTypes(getAsStringSet(o, RESPONSE_TYPES)); + c.setPolicyUri(getAsString(o, POLICY_URI)); + c.setJwksUri(getAsString(o, JWKS_URI)); + + JsonElement jwksEl = o.get(JWKS); + if (jwksEl != null && jwksEl.isJsonObject()) { + try { + JWKSet jwks = JWKSet.parse(jwksEl.toString()); // we have to pass this through Nimbus's parser as a string + c.setJwks(jwks); + } catch (ParseException e) { + logger.error("Unable to parse JWK Set for client", e); + return null; + } + } // OIDC Additions - String appType = getAsString(o, "application_type"); + String appType = getAsString(o, APPLICATION_TYPE); if (appType != null) { c.setApplicationType(AppType.getByValue(appType)); } - c.setSectorIdentifierUri(getAsString(o, "sector_identifier_uri")); + c.setSectorIdentifierUri(getAsString(o, SECTOR_IDENTIFIER_URI)); - String subjectType = getAsString(o, "subject_type"); + String subjectType = getAsString(o, SUBJECT_TYPE); if (subjectType != null) { c.setSubjectType(SubjectType.getByValue(subjectType)); } - c.setRequestObjectSigningAlg(getAsJwsAlgorithm(o, "request_object_signing_alg")); + c.setRequestObjectSigningAlg(getAsJwsAlgorithm(o, REQUEST_OBJECT_SIGNING_ALG)); - c.setUserInfoSignedResponseAlg(getAsJwsAlgorithm(o, "userinfo_signed_response_alg")); - c.setUserInfoEncryptedResponseAlg(getAsJweAlgorithm(o, "userinfo_encrypted_response_alg")); - c.setUserInfoEncryptedResponseEnc(getAsJweEncryptionMethod(o, "userinfo_encrypted_response_enc")); + c.setUserInfoSignedResponseAlg(getAsJwsAlgorithm(o, USERINFO_SIGNED_RESPONSE_ALG)); + c.setUserInfoEncryptedResponseAlg(getAsJweAlgorithm(o, USERINFO_ENCRYPTED_RESPONSE_ALG)); + c.setUserInfoEncryptedResponseEnc(getAsJweEncryptionMethod(o, USERINFO_ENCRYPTED_RESPONSE_ENC)); - c.setIdTokenSignedResponseAlg(getAsJwsAlgorithm(o, "id_token_signed_response_alg")); - c.setIdTokenEncryptedResponseAlg(getAsJweAlgorithm(o, "id_token_encrypted_response_alg")); - c.setIdTokenEncryptedResponseEnc(getAsJweEncryptionMethod(o, "id_token_encrypted_response_enc")); + c.setIdTokenSignedResponseAlg(getAsJwsAlgorithm(o, ID_TOKEN_SIGNED_RESPONSE_ALG)); + c.setIdTokenEncryptedResponseAlg(getAsJweAlgorithm(o, ID_TOKEN_ENCRYPTED_RESPONSE_ALG)); + c.setIdTokenEncryptedResponseEnc(getAsJweEncryptionMethod(o, ID_TOKEN_ENCRYPTED_RESPONSE_ENC)); - c.setTokenEndpointAuthSigningAlg(getAsJwsAlgorithm(o, "token_endpoint_auth_signing_alg")); + c.setTokenEndpointAuthSigningAlg(getAsJwsAlgorithm(o, TOKEN_ENDPOINT_AUTH_SIGNING_ALG)); - if (o.has("default_max_age")) { - if (o.get("default_max_age").isJsonPrimitive()) { - c.setDefaultMaxAge(o.get("default_max_age").getAsInt()); + if (o.has(DEFAULT_MAX_AGE)) { + if (o.get(DEFAULT_MAX_AGE).isJsonPrimitive()) { + c.setDefaultMaxAge(o.get(DEFAULT_MAX_AGE).getAsInt()); } } - if (o.has("require_auth_time")) { - if (o.get("require_auth_time").isJsonPrimitive()) { - c.setRequireAuthTime(o.get("require_auth_time").getAsBoolean()); + if (o.has(REQUIRE_AUTH_TIME)) { + if (o.get(REQUIRE_AUTH_TIME).isJsonPrimitive()) { + c.setRequireAuthTime(o.get(REQUIRE_AUTH_TIME).getAsBoolean()); } } - c.setDefaultACRvalues(getAsStringSet(o, "default_acr_values")); - c.setInitiateLoginUri(getAsString(o, "initiate_login_uri")); - c.setPostLogoutRedirectUri(getAsString(o, "post_logout_redirect_uri")); - c.setRequestUris(getAsStringSet(o, "request_uris")); + c.setDefaultACRvalues(getAsStringSet(o, DEFAULT_ACR_VALUES)); + c.setInitiateLoginUri(getAsString(o, INITIATE_LOGIN_URI)); + c.setPostLogoutRedirectUris(getAsStringSet(o, POST_LOGOUT_REDIRECT_URIS)); + c.setRequestUris(getAsStringSet(o, REQUEST_URIS)); + + c.setClaimsRedirectUris(getAsStringSet(o, CLAIMS_REDIRECT_URIS)); + + c.setCodeChallengeMethod(getAsPkceAlgorithm(o, CODE_CHALLENGE_METHOD)); + + c.setSoftwareId(getAsString(o, SOFTWARE_ID)); + c.setSoftwareVersion(getAsString(o, SOFTWARE_VERSION)); + + // note that this does not process or validate the software statement, that's handled in other components + String softwareStatement = getAsString(o, SOFTWARE_STATEMENT); + if (!Strings.isNullOrEmpty(softwareStatement)) { + try { + JWT softwareStatementJwt = JWTParser.parse(softwareStatement); + c.setSoftwareStatement(softwareStatementJwt); + } catch (ParseException e) { + logger.warn("Error parsing software statement", e); + return null; + } + } + + return c; } else { @@ -149,17 +239,23 @@ public static RegisteredClient parseRegistered(String jsonString) { JsonElement jsonEl = parser.parse(jsonString); + return parseRegistered(jsonEl); + } + + public static RegisteredClient parseRegistered(JsonElement jsonEl) { if (jsonEl.isJsonObject()) { JsonObject o = jsonEl.getAsJsonObject(); - ClientDetailsEntity c = parse(jsonString); + ClientDetailsEntity c = parse(jsonEl); RegisteredClient rc = new RegisteredClient(c); // get any fields from the registration - rc.setRegistrationAccessToken(getAsString(o, "registration_access_token")); - rc.setRegistrationClientUri(getAsString(o, "registration_client_uri")); - rc.setClientIdIssuedAt(getAsDate(o, "client_id_issued_at")); - rc.setClientSecretExpiresAt(getAsDate(o, "client_secret_expires_at")); + rc.setRegistrationAccessToken(getAsString(o, REGISTRATION_ACCESS_TOKEN)); + rc.setRegistrationClientUri(getAsString(o, REGISTRATION_CLIENT_URI)); + rc.setClientIdIssuedAt(getAsDate(o, CLIENT_ID_ISSUED_AT)); + rc.setClientSecretExpiresAt(getAsDate(o, CLIENT_SECRET_EXPIRES_AT)); + + rc.setSource(o); return rc; } else { @@ -174,69 +270,96 @@ public static RegisteredClient parseRegistered(String jsonString) { * @return */ public static JsonObject serialize(RegisteredClient c) { - JsonObject o = new JsonObject(); - o.addProperty("client_id", c.getClientId()); - if (c.getClientSecret() != null) { - o.addProperty("client_secret", c.getClientSecret()); + if (c.getSource() != null) { + // if we have the original object, just use that + return c.getSource(); + } else { + + JsonObject o = new JsonObject(); - if (c.getClientSecretExpiresAt() == null) { - o.addProperty("client_secret_expires_at", 0); // TODO: do we want to let secrets expire? - } else { - o.addProperty("client_secret_expires_at", c.getClientSecretExpiresAt().getTime() / 1000L); + o.addProperty(CLIENT_ID, c.getClientId()); + if (c.getClientSecret() != null) { + o.addProperty(CLIENT_SECRET, c.getClientSecret()); + + if (c.getClientSecretExpiresAt() == null) { + o.addProperty(CLIENT_SECRET_EXPIRES_AT, 0); // TODO: do we want to let secrets expire? + } else { + o.addProperty(CLIENT_SECRET_EXPIRES_AT, c.getClientSecretExpiresAt().getTime() / 1000L); + } } - } - if (c.getClientIdIssuedAt() != null) { - o.addProperty("client_id_issued_at", c.getClientIdIssuedAt().getTime() / 1000L); - } else if (c.getCreatedAt() != null) { - o.addProperty("client_id_issued_at", c.getCreatedAt().getTime() / 1000L); - } - if (c.getRegistrationAccessToken() != null) { - o.addProperty("registration_access_token", c.getRegistrationAccessToken()); - } + if (c.getClientIdIssuedAt() != null) { + o.addProperty(CLIENT_ID_ISSUED_AT, c.getClientIdIssuedAt().getTime() / 1000L); + } else if (c.getCreatedAt() != null) { + o.addProperty(CLIENT_ID_ISSUED_AT, c.getCreatedAt().getTime() / 1000L); + } + if (c.getRegistrationAccessToken() != null) { + o.addProperty(REGISTRATION_ACCESS_TOKEN, c.getRegistrationAccessToken()); + } - if (c.getRegistrationClientUri() != null) { - o.addProperty("registration_client_uri", c.getRegistrationClientUri()); - } + if (c.getRegistrationClientUri() != null) { + o.addProperty(REGISTRATION_CLIENT_URI, c.getRegistrationClientUri()); + } - // add in all other client properties - - // OAuth DynReg - o.add("redirect_uris", getAsArray(c.getRedirectUris())); - o.addProperty("client_name", c.getClientName()); - o.addProperty("client_uri", c.getClientUri()); - o.addProperty("logo_uri", c.getLogoUri()); - o.add("contacts", getAsArray(c.getContacts())); - o.addProperty("tos_uri", c.getTosUri()); - o.addProperty("token_endpoint_auth_method", c.getTokenEndpointAuthMethod() != null ? c.getTokenEndpointAuthMethod().getValue() : null); - o.addProperty("scope", c.getScope() != null ? Joiner.on(" ").join(c.getScope()) : null); - o.add("grant_types", getAsArray(c.getGrantTypes())); - o.add("response_types", getAsArray(c.getResponseTypes())); - o.addProperty("policy_uri", c.getPolicyUri()); - o.addProperty("jwks_uri", c.getJwksUri()); - - // OIDC Registration - o.addProperty("application_type", c.getApplicationType() != null ? c.getApplicationType().getValue() : null); - o.addProperty("sector_identifier_uri", c.getSectorIdentifierUri()); - o.addProperty("subject_type", c.getSubjectType() != null ? c.getSubjectType().getValue() : null); - o.addProperty("request_object_signing_alg", c.getRequestObjectSigningAlg() != null ? c.getRequestObjectSigningAlg().getName() : null); - o.addProperty("userinfo_signed_response_alg", c.getUserInfoSignedResponseAlg() != null ? c.getUserInfoSignedResponseAlg().getName() : null); - o.addProperty("userinfo_encrypted_response_alg", c.getUserInfoEncryptedResponseAlg() != null ? c.getUserInfoEncryptedResponseAlg().getName() : null); - o.addProperty("userinfo_encrypted_response_enc", c.getUserInfoEncryptedResponseEnc() != null ? c.getUserInfoEncryptedResponseEnc().getName() : null); - o.addProperty("id_token_signed_response_alg", c.getIdTokenSignedResponseAlg() != null ? c.getIdTokenSignedResponseAlg().getName() : null); - o.addProperty("id_token_encrypted_response_alg", c.getIdTokenEncryptedResponseAlg() != null ? c.getIdTokenEncryptedResponseAlg().getName() : null); - o.addProperty("id_token_encrypted_response_enc", c.getIdTokenEncryptedResponseEnc() != null ? c.getIdTokenEncryptedResponseEnc().getName() : null); - o.addProperty("token_endpoint_auth_signing_alg", c.getTokenEndpointAuthSigningAlg() != null ? c.getTokenEndpointAuthSigningAlg().getName() : null); - o.addProperty("default_max_age", c.getDefaultMaxAge()); - o.addProperty("require_auth_time", c.getRequireAuthTime()); - o.add("default_acr_values", getAsArray(c.getDefaultACRvalues())); - o.addProperty("initiate_login_uri", c.getInitiateLoginUri()); - o.addProperty("post_logout_redirect_uri", c.getPostLogoutRedirectUri()); - o.add("request_uris", getAsArray(c.getRequestUris())); - return o; - } + // add in all other client properties + // OAuth DynReg + o.add(REDIRECT_URIS, getAsArray(c.getRedirectUris())); + o.addProperty(CLIENT_NAME, c.getClientName()); + o.addProperty(CLIENT_URI, c.getClientUri()); + o.addProperty(LOGO_URI, c.getLogoUri()); + o.add(CONTACTS, getAsArray(c.getContacts())); + o.addProperty(TOS_URI, c.getTosUri()); + o.addProperty(TOKEN_ENDPOINT_AUTH_METHOD, c.getTokenEndpointAuthMethod() != null ? c.getTokenEndpointAuthMethod().getValue() : null); + o.addProperty(SCOPE, c.getScope() != null ? Joiner.on(SCOPE_SEPARATOR).join(c.getScope()) : null); + o.add(GRANT_TYPES, getAsArray(c.getGrantTypes())); + o.add(RESPONSE_TYPES, getAsArray(c.getResponseTypes())); + o.addProperty(POLICY_URI, c.getPolicyUri()); + o.addProperty(JWKS_URI, c.getJwksUri()); + + // get the JWKS sub-object + if (c.getJwks() != null) { + // We have to re-parse it into GSON because Nimbus uses a different parser + JsonElement jwks = parser.parse(c.getJwks().toString()); + o.add(JWKS, jwks); + } else { + o.add(JWKS, null); + } + // OIDC Registration + o.addProperty(APPLICATION_TYPE, c.getApplicationType() != null ? c.getApplicationType().getValue() : null); + o.addProperty(SECTOR_IDENTIFIER_URI, c.getSectorIdentifierUri()); + o.addProperty(SUBJECT_TYPE, c.getSubjectType() != null ? c.getSubjectType().getValue() : null); + o.addProperty(REQUEST_OBJECT_SIGNING_ALG, c.getRequestObjectSigningAlg() != null ? c.getRequestObjectSigningAlg().getName() : null); + o.addProperty(USERINFO_SIGNED_RESPONSE_ALG, c.getUserInfoSignedResponseAlg() != null ? c.getUserInfoSignedResponseAlg().getName() : null); + o.addProperty(USERINFO_ENCRYPTED_RESPONSE_ALG, c.getUserInfoEncryptedResponseAlg() != null ? c.getUserInfoEncryptedResponseAlg().getName() : null); + o.addProperty(USERINFO_ENCRYPTED_RESPONSE_ENC, c.getUserInfoEncryptedResponseEnc() != null ? c.getUserInfoEncryptedResponseEnc().getName() : null); + o.addProperty(ID_TOKEN_SIGNED_RESPONSE_ALG, c.getIdTokenSignedResponseAlg() != null ? c.getIdTokenSignedResponseAlg().getName() : null); + o.addProperty(ID_TOKEN_ENCRYPTED_RESPONSE_ALG, c.getIdTokenEncryptedResponseAlg() != null ? c.getIdTokenEncryptedResponseAlg().getName() : null); + o.addProperty(ID_TOKEN_ENCRYPTED_RESPONSE_ENC, c.getIdTokenEncryptedResponseEnc() != null ? c.getIdTokenEncryptedResponseEnc().getName() : null); + o.addProperty(TOKEN_ENDPOINT_AUTH_SIGNING_ALG, c.getTokenEndpointAuthSigningAlg() != null ? c.getTokenEndpointAuthSigningAlg().getName() : null); + o.addProperty(DEFAULT_MAX_AGE, c.getDefaultMaxAge()); + o.addProperty(REQUIRE_AUTH_TIME, c.getRequireAuthTime()); + o.add(DEFAULT_ACR_VALUES, getAsArray(c.getDefaultACRvalues())); + o.addProperty(INITIATE_LOGIN_URI, c.getInitiateLoginUri()); + o.add(POST_LOGOUT_REDIRECT_URIS, getAsArray(c.getPostLogoutRedirectUris())); + o.add(REQUEST_URIS, getAsArray(c.getRequestUris())); + + o.add(CLAIMS_REDIRECT_URIS, getAsArray(c.getClaimsRedirectUris())); + + o.addProperty(CODE_CHALLENGE_METHOD, c.getCodeChallengeMethod() != null ? c.getCodeChallengeMethod().getName() : null); + + o.addProperty(SOFTWARE_ID, c.getSoftwareId()); + o.addProperty(SOFTWARE_VERSION, c.getSoftwareVersion()); + + if (c.getSoftwareStatement() != null) { + o.addProperty(SOFTWARE_STATEMENT, c.getSoftwareStatement().serialize()); + } + + return o; + } + + } } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationBeanLocaleResolver.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationBeanLocaleResolver.java new file mode 100644 index 0000000000..c351e228a4 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationBeanLocaleResolver.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +/** + * + */ +package org.mitre.openid.connect.config; + +import java.util.Locale; +import java.util.TimeZone; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.i18n.LocaleContext; +import org.springframework.context.i18n.TimeZoneAwareLocaleContext; +import org.springframework.web.servlet.i18n.AbstractLocaleContextResolver; + +/** + * + * Resolve the server's locale from the injected ConfigurationPropertiesBean. + * + * @author jricher + * + */ +public class ConfigurationBeanLocaleResolver extends AbstractLocaleContextResolver { + + @Autowired + private ConfigurationPropertiesBean config; + + @Override + protected Locale getDefaultLocale() { + if (config.getLocale() != null) { + return config.getLocale(); + } else { + return super.getDefaultLocale(); + } + } + + @Override + public LocaleContext resolveLocaleContext(HttpServletRequest request) { + return new TimeZoneAwareLocaleContext() { + @Override + public Locale getLocale() { + return getDefaultLocale(); + } + @Override + public TimeZone getTimeZone() { + return getDefaultTimeZone(); + } + }; + } + + @Override + public void setLocaleContext(HttpServletRequest request, HttpServletResponse response, LocaleContext localeContext) { + throw new UnsupportedOperationException("Cannot change fixed locale - use a different locale resolution strategy"); + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java index 3258a4bd2a..9d286518f1 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ConfigurationPropertiesBean.java @@ -1,58 +1,98 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.config; +import java.util.List; +import java.util.Locale; + import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.BeanCreationException; import org.springframework.util.StringUtils; +import com.google.common.collect.Lists; +import com.google.gson.Gson; + /** * Bean to hold configuration information that must be injected into various parts * of our application. Set all of the properties here, and autowire a reference * to this bean if you need access to any configuration properties. - * + * * @author AANGANES * */ public class ConfigurationPropertiesBean { - private static Logger logger = LoggerFactory.getLogger(ConfigurationPropertiesBean.class); + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(ConfigurationPropertiesBean.class); private String issuer; private String topbarTitle; + private String shortTopbarTitle; + private String logoImageUrl; + private Long regTokenLifeTime; + + private Long rqpTokenLifeTime; + + private boolean forceHttps = false; // by default we just log a warning for HTTPS deployment + + private Locale locale = Locale.ENGLISH; // we default to the english translation + + private List languageNamespaces = Lists.newArrayList("messages"); + + private boolean dualClient = false; + + private boolean heartMode = false; + + private boolean allowCompleteDeviceCodeUri = false; + public ConfigurationPropertiesBean() { } /** * Endpoints protected by TLS must have https scheme in the URI. + * @throws HttpsUrlRequiredException */ @PostConstruct - public void checkForHttps() { + public void checkConfigConsistency() { if (!StringUtils.startsWithIgnoreCase(issuer, "https")) { - logger.warn("Configured issuer url is not using https scheme."); + if (this.forceHttps) { + logger.error("Configured issuer url is not using https scheme. Server will be shut down!"); + throw new BeanCreationException("Issuer is not using https scheme as required: " + issuer); + } + else { + logger.warn("\n\n**\n** WARNING: Configured issuer url is not using https scheme.\n**\n\n"); + } + } + + if (languageNamespaces == null || languageNamespaces.isEmpty()) { + logger.error("No configured language namespaces! Text rendering will fail!"); } } @@ -84,6 +124,17 @@ public void setTopbarTitle(String topbarTitle) { this.topbarTitle = topbarTitle; } + /** + * @return If shortTopbarTitle is undefined, returns topbarTitle. + */ + public String getShortTopbarTitle() { + return shortTopbarTitle == null ? topbarTitle : shortTopbarTitle; + } + + public void setShortTopbarTitle(String shortTopbarTitle) { + this.shortTopbarTitle = shortTopbarTitle; + } + /** * @return the logoImageUrl */ @@ -98,4 +149,128 @@ public void setLogoImageUrl(String logoImageUrl) { this.logoImageUrl = logoImageUrl; } + /** + * @return the regTokenLifeTime + */ + public Long getRegTokenLifeTime() { + return regTokenLifeTime; + } + + /** + * @param regTokenLifeTime the registration token lifetime to set in seconds + */ + public void setRegTokenLifeTime(Long regTokenLifeTime) { + this.regTokenLifeTime = regTokenLifeTime; + } + + /** + * @return the rqpTokenLifeTime + */ + public Long getRqpTokenLifeTime() { + return rqpTokenLifeTime; + } + + /** + * @param rqpTokenLifeTime the rqpTokenLifeTime to set + */ + public void setRqpTokenLifeTime(Long rqpTokenLifeTime) { + this.rqpTokenLifeTime = rqpTokenLifeTime; + } + + public boolean isForceHttps() { + return forceHttps; + } + + public void setForceHttps(boolean forceHttps) { + this.forceHttps = forceHttps; + } + + /** + * @return the locale + */ + public Locale getLocale() { + return locale; + } + + /** + * @param locale the locale to set + */ + public void setLocale(Locale locale) { + this.locale = locale; + } + + /** + * @return the languageNamespaces + */ + public List getLanguageNamespaces() { + return languageNamespaces; + } + + /** + * @param languageNamespaces the languageNamespaces to set + */ + public void setLanguageNamespaces(List languageNamespaces) { + this.languageNamespaces = languageNamespaces; + } + + /** + * @return true if dual client is configured, otherwise false + */ + public boolean isDualClient() { + if (isHeartMode()) { + return false; // HEART mode is incompatible with dual client mode + } else { + return dualClient; + } + } + + /** + * @param dualClient the dual client configuration + */ + public void setDualClient(boolean dualClient) { + this.dualClient = dualClient; + } + + /** + * Get the list of namespaces as a JSON string, for injection into the JavaScript UI + * @return + */ + public String getLanguageNamespacesString() { + return new Gson().toJson(getLanguageNamespaces()); + } + + /** + * Get the default namespace (first in the nonempty list) + */ + public String getDefaultLanguageNamespace() { + return getLanguageNamespaces().get(0); + } + + /** + * @return the heartMode + */ + public boolean isHeartMode() { + return heartMode; + } + + /** + * @param heartMode the heartMode to set + */ + public void setHeartMode(boolean heartMode) { + this.heartMode = heartMode; + } + + /** + * @return the allowCompleteDeviceCodeUri + */ + public boolean isAllowCompleteDeviceCodeUri() { + return allowCompleteDeviceCodeUri; + } + + /** + * @param allowCompleteDeviceCodeUri the allowCompleteDeviceCodeUri to set + */ + public void setAllowCompleteDeviceCodeUri(boolean allowCompleteDeviceCodeUri) { + this.allowCompleteDeviceCodeUri = allowCompleteDeviceCodeUri; + } } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/JWKSetEditor.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/JWKSetEditor.java new file mode 100644 index 0000000000..03c853498e --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/JWKSetEditor.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.config; + +import java.beans.PropertyEditorSupport; +import java.text.ParseException; + +import com.google.common.base.Strings; +import com.nimbusds.jose.jwk.JWKSet; + +/** + * Allows JWK Set strings to be used in XML configurations. + * + * @author jricher + * + */ +public class JWKSetEditor extends PropertyEditorSupport { + + @Override + public void setAsText(String text) throws IllegalArgumentException { + if (!Strings.isNullOrEmpty(text)) { + try { + setValue(JWKSet.parse(text)); + } catch (ParseException e) { + throw new IllegalArgumentException(e); + } + } else { + setValue(null); + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java index 78e6599caa..2325e3fd36 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/ServerConfiguration.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.config; import java.util.List; @@ -25,16 +26,16 @@ /** - * + * * Container class for a client's view of a server's configuration - * + * * @author nemonik, jricher - * + * */ public class ServerConfiguration { /* - * + * issuer REQUIRED. URL using the https scheme with no query or fragment component that the OP asserts as its Issuer Identifier. authorization_endpoint @@ -175,12 +176,6 @@ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values private String revocationEndpointUri; - public String getRevocationEndpointUri() { - return revocationEndpointUri; - } - public void setRevocationEndpointUri(String revocationEndpointUri) { - this.revocationEndpointUri = revocationEndpointUri; - } private String checkSessionIframe; private String endSessionEndpoint; private List scopesSupported; @@ -211,6 +206,20 @@ public void setRevocationEndpointUri(String revocationEndpointUri) { private Boolean requireRequestUriRegistration; private String opPolicyUri; private String opTosUri; + + // + // extensions to the discoverable methods + // + + // how do we send the access token to the userinfo endpoint? + private UserInfoTokenMethod userInfoTokenMethod; + + public enum UserInfoTokenMethod { + HEADER, + FORM, + QUERY; + } + /** * @return the authorizationEndpointUri */ @@ -655,6 +664,20 @@ public String getOpTosUri() { public void setOpTosUri(String opTosUri) { this.opTosUri = opTosUri; } + + public String getRevocationEndpointUri() { + return revocationEndpointUri; + } + public void setRevocationEndpointUri(String revocationEndpointUri) { + this.revocationEndpointUri = revocationEndpointUri; + } + + public UserInfoTokenMethod getUserInfoTokenMethod() { + return userInfoTokenMethod; + } + public void setUserInfoTokenMethod(UserInfoTokenMethod userInfoTokenMethod) { + this.userInfoTokenMethod = userInfoTokenMethod; + } @Override public int hashCode() { final int prime = 31; diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/config/UIConfiguration.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/UIConfiguration.java new file mode 100644 index 0000000000..6e4900640e --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/config/UIConfiguration.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.config; + +import java.util.Set; + +/** + * + * Bean for UI (front-end) configuration to be read at start-up. + * + * @author jricher + * + */ +public class UIConfiguration { + + private Set jsFiles; + + /** + * @return the jsFiles + */ + public Set getJsFiles() { + return jsFiles; + } + /** + * @param jsFiles the jsFiles to set + */ + public void setJsFiles(Set jsFiles) { + this.jsFiles = jsFiles; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/Address.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/Address.java index 1d8154faf2..81fc308faf 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/Address.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/Address.java @@ -1,233 +1,90 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.model; -import javax.persistence.Basic; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import java.io.Serializable; -@Entity -@Table(name="address") -public class Address { - - - private Long id; - private String formatted; - private String streetAddress; - private String locality; - private String region; - private String postalCode; - private String country; +public interface Address extends Serializable { /** - * Empty constructor + * Get the system-specific ID of the Address object + * @return */ - public Address() { - - } + public Long getId(); /** - * @return the formatted address string + * @return the formatted address */ - @Basic - @Column(name = "formatted") - public String getFormatted() { - return formatted; - } + public String getFormatted(); + /** * @param formatted the formatted address to set */ - public void setFormatted(String formatted) { - this.formatted = formatted; - } + public void setFormatted(String formatted); + /** * @return the streetAddress */ - @Basic - @Column(name="street_address") - public String getStreetAddress() { - return streetAddress; - } + public String getStreetAddress(); + /** * @param streetAddress the streetAddress to set */ - public void setStreetAddress(String streetAddress) { - this.streetAddress = streetAddress; - } + public void setStreetAddress(String streetAddress); + /** * @return the locality */ - @Basic - @Column(name = "locality") - public String getLocality() { - return locality; - } + public String getLocality(); + /** * @param locality the locality to set */ - public void setLocality(String locality) { - this.locality = locality; - } + public void setLocality(String locality); + /** * @return the region */ - @Basic - @Column(name = "region") - public String getRegion() { - return region; - } + public String getRegion(); + /** * @param region the region to set */ - public void setRegion(String region) { - this.region = region; - } + public void setRegion(String region); + /** * @return the postalCode */ - @Basic - @Column(name="postal_code") - public String getPostalCode() { - return postalCode; - } + public String getPostalCode(); + /** * @param postalCode the postalCode to set */ - public void setPostalCode(String postalCode) { - this.postalCode = postalCode; - } - /** - * @return the country - */ - @Basic - @Column(name = "country") - public String getCountry() { - return country; - } - /** - * @param country the country to set - */ - public void setCountry(String country) { - this.country = country; - } + public void setPostalCode(String postalCode); /** - * @return the id + * @return the country */ - @Id - @GeneratedValue(strategy=GenerationType.IDENTITY) - @Column(name = "id") - public Long getId() { - return id; - } + public String getCountry(); /** - * @param id the id to set - */ - public void setId(Long id) { - this.id = id; - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((country == null) ? 0 : country.hashCode()); - result = prime * result + ((formatted == null) ? 0 : formatted.hashCode()); - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + ((locality == null) ? 0 : locality.hashCode()); - result = prime * result + ((postalCode == null) ? 0 : postalCode.hashCode()); - result = prime * result + ((region == null) ? 0 : region.hashCode()); - result = prime * result + ((streetAddress == null) ? 0 : streetAddress.hashCode()); - return result; - } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) + * @param country the country to set */ - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (!(obj instanceof Address)) { - return false; - } - Address other = (Address) obj; - if (country == null) { - if (other.country != null) { - return false; - } - } else if (!country.equals(other.country)) { - return false; - } - if (formatted == null) { - if (other.formatted != null) { - return false; - } - } else if (!formatted.equals(other.formatted)) { - return false; - } - if (id == null) { - if (other.id != null) { - return false; - } - } else if (!id.equals(other.id)) { - return false; - } - if (locality == null) { - if (other.locality != null) { - return false; - } - } else if (!locality.equals(other.locality)) { - return false; - } - if (postalCode == null) { - if (other.postalCode != null) { - return false; - } - } else if (!postalCode.equals(other.postalCode)) { - return false; - } - if (region == null) { - if (other.region != null) { - return false; - } - } else if (!region.equals(other.region)) { - return false; - } - if (streetAddress == null) { - if (other.streetAddress != null) { - return false; - } - } else if (!streetAddress.equals(other.streetAddress)) { - return false; - } - return true; - } + public void setCountry(String country); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java index c02ad4a18b..c8b530c1ef 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ApprovedSite.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.model; import java.util.Date; import java.util.Set; import javax.persistence.Basic; -import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; @@ -30,28 +30,30 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.Transient; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; - -import com.google.common.collect.Sets; - @Entity @Table(name="approved_site") @NamedQueries({ - @NamedQuery(name = "ApprovedSite.getAll", query = "select a from ApprovedSite a"), - @NamedQuery(name = "ApprovedSite.getByUserId", query = "select a from ApprovedSite a where a.userId = :userId"), - @NamedQuery(name = "ApprovedSite.getByClientId", query = "select a from ApprovedSite a where a.clientId = :clientId"), - @NamedQuery(name = "ApprovedSite.getByClientIdAndUserId", query = "select a from ApprovedSite a where a.clientId = :clientId and a.userId = :userId") + @NamedQuery(name = ApprovedSite.QUERY_ALL, query = "select a from ApprovedSite a"), + @NamedQuery(name = ApprovedSite.QUERY_BY_USER_ID, query = "select a from ApprovedSite a where a.userId = :" + ApprovedSite.PARAM_USER_ID), + @NamedQuery(name = ApprovedSite.QUERY_BY_CLIENT_ID, query = "select a from ApprovedSite a where a.clientId = :" + ApprovedSite.PARAM_CLIENT_ID), + @NamedQuery(name = ApprovedSite.QUERY_BY_CLIENT_ID_AND_USER_ID, query = "select a from ApprovedSite a where a.clientId = :" + ApprovedSite.PARAM_CLIENT_ID + " and a.userId = :" + ApprovedSite.PARAM_USER_ID) }) public class ApprovedSite { + public static final String QUERY_BY_CLIENT_ID_AND_USER_ID = "ApprovedSite.getByClientIdAndUserId"; + public static final String QUERY_BY_CLIENT_ID = "ApprovedSite.getByClientId"; + public static final String QUERY_BY_USER_ID = "ApprovedSite.getByUserId"; + public static final String QUERY_ALL = "ApprovedSite.getAll"; + + public static final String PARAM_CLIENT_ID = "clientId"; + public static final String PARAM_USER_ID = "userId"; + // unique id private Long id; @@ -74,12 +76,6 @@ public class ApprovedSite { // this should include all information for what data to access private Set allowedScopes; - // If this AP is a WS, link to the WS - private WhitelistedSite whitelistedSite; - - //Link to any access tokens approved through this stored decision - private Set approvedAccessTokens = Sets.newHashSet(); - /** * Empty constructor */ @@ -207,26 +203,6 @@ public void setTimeoutDate(Date timeoutDate) { this.timeoutDate = timeoutDate; } - /** - * Does this AP entry correspond to a WS? - * @return - */ - @Transient - public Boolean getIsWhitelisted() { - return (whitelistedSite != null); - } - - - @ManyToOne - @JoinColumn(name="whitelisted_site_id") - public WhitelistedSite getWhitelistedSite() { - return whitelistedSite; - } - - public void setWhitelistedSite(WhitelistedSite whitelistedSite) { - this.whitelistedSite = whitelistedSite; - } - /** * Has this approval expired? * @return @@ -245,16 +221,4 @@ public boolean isExpired() { } } - @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) - @JoinColumn(name="approved_site_id") - public Set getApprovedAccessTokens() { - return approvedAccessTokens; - } - - /** - * @param approvedAccessTokens the approvedAccessTokens to set - */ - public void setApprovedAccessTokens(Set approvedAccessTokens) { - this.approvedAccessTokens = approvedAccessTokens; - } } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java index 1fb227cbff..bfa4f47667 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/BlacklistedSite.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.model; @@ -36,10 +37,12 @@ @Entity @Table(name="blacklisted_site") @NamedQueries({ - @NamedQuery(name = "BlacklistedSite.getAll", query = "select b from BlacklistedSite b") + @NamedQuery(name = BlacklistedSite.QUERY_ALL, query = "select b from BlacklistedSite b") }) public class BlacklistedSite { + public static final String QUERY_ALL = "BlacklistedSite.getAll"; + // unique id private Long id; diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/CachedImage.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/CachedImage.java new file mode 100644 index 0000000000..b4af45c14b --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/CachedImage.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.model; + +/** + * @author jricher + * + */ +public class CachedImage { + + private byte[] data; + private String contentType; + private long length; + + /** + * @return the data + */ + public byte[] getData() { + return data; + } + /** + * @param data the data to set + */ + public void setData(byte[] data) { + this.data = data; + } + /** + * @return the contentType + */ + public String getContentType() { + return contentType; + } + /** + * @param contentType the contentType to set + */ + public void setContentType(String contentType) { + this.contentType = contentType; + } + /** + * @return the length + */ + public long getLength() { + return length; + } + /** + * @param length the length to set + */ + public void setLength(long length) { + this.length = length; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ClientStat.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ClientStat.java new file mode 100644 index 0000000000..2b817ee791 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/ClientStat.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.model; + +/** + * @author jricher + * + */ +public class ClientStat { + + private Integer approvedSiteCount; + + /** + * @return the count + */ + public Integer getApprovedSiteCount() { + return approvedSiteCount; + } + + /** + * @param count the count to set + */ + public void setApprovedSiteCount(Integer count) { + this.approvedSiteCount = count; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultAddress.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultAddress.java new file mode 100644 index 0000000000..ecdda8bdb8 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultAddress.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.openid.connect.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name="address") +public class DefaultAddress implements Address { + + private static final long serialVersionUID = -1304880008685206811L; + + private Long id; + private String formatted; + private String streetAddress; + private String locality; + private String region; + private String postalCode; + private String country; + + /** + * Empty constructor + */ + public DefaultAddress() { + + } + + /** + * Copy constructor from an existing address. + * @param address + */ + public DefaultAddress(Address address) { + setFormatted(address.getFormatted()); + setStreetAddress(address.getStreetAddress()); + setLocality(address.getLocality()); + setRegion(address.getRegion()); + setPostalCode(address.getPostalCode()); + setCountry(address.getCountry()); + } + + /** + * @return the formatted address string + */ + @Override + @Basic + @Column(name = "formatted") + public String getFormatted() { + return formatted; + } + /** + * @param formatted the formatted address to set + */ + @Override + public void setFormatted(String formatted) { + this.formatted = formatted; + } + /** + * @return the streetAddress + */ + @Override + @Basic + @Column(name="street_address") + public String getStreetAddress() { + return streetAddress; + } + /** + * @param streetAddress the streetAddress to set + */ + @Override + public void setStreetAddress(String streetAddress) { + this.streetAddress = streetAddress; + } + /** + * @return the locality + */ + @Override + @Basic + @Column(name = "locality") + public String getLocality() { + return locality; + } + /** + * @param locality the locality to set + */ + @Override + public void setLocality(String locality) { + this.locality = locality; + } + /** + * @return the region + */ + @Override + @Basic + @Column(name = "region") + public String getRegion() { + return region; + } + /** + * @param region the region to set + */ + @Override + public void setRegion(String region) { + this.region = region; + } + /** + * @return the postalCode + */ + @Override + @Basic + @Column(name="postal_code") + public String getPostalCode() { + return postalCode; + } + /** + * @param postalCode the postalCode to set + */ + @Override + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + /** + * @return the country + */ + @Override + @Basic + @Column(name = "country") + public String getCountry() { + return country; + } + /** + * @param country the country to set + */ + @Override + public void setCountry(String country) { + this.country = country; + } + + /** + * @return the id + */ + @Override + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((country == null) ? 0 : country.hashCode()); + result = prime * result + ((formatted == null) ? 0 : formatted.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((locality == null) ? 0 : locality.hashCode()); + result = prime * result + ((postalCode == null) ? 0 : postalCode.hashCode()); + result = prime * result + ((region == null) ? 0 : region.hashCode()); + result = prime * result + ((streetAddress == null) ? 0 : streetAddress.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof DefaultAddress)) { + return false; + } + DefaultAddress other = (DefaultAddress) obj; + if (country == null) { + if (other.country != null) { + return false; + } + } else if (!country.equals(other.country)) { + return false; + } + if (formatted == null) { + if (other.formatted != null) { + return false; + } + } else if (!formatted.equals(other.formatted)) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (locality == null) { + if (other.locality != null) { + return false; + } + } else if (!locality.equals(other.locality)) { + return false; + } + if (postalCode == null) { + if (other.postalCode != null) { + return false; + } + } else if (!postalCode.equals(other.postalCode)) { + return false; + } + if (region == null) { + if (other.region != null) { + return false; + } + } else if (!region.equals(other.region)) { + return false; + } + if (streetAddress == null) { + if (other.streetAddress != null) { + return false; + } + } else if (!streetAddress.equals(other.streetAddress)) { + return false; + } + return true; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java index d15df807a8..8b73f3689e 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/DefaultUserInfo.java @@ -1,23 +1,30 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.model; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + import javax.persistence.Basic; +import javax.persistence.CascadeType; import javax.persistence.Column; +import javax.persistence.Convert; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -28,17 +35,27 @@ import javax.persistence.OneToOne; import javax.persistence.Table; +import org.mitre.openid.connect.model.convert.JsonObjectStringConverter; + import com.google.gson.JsonObject; +import com.google.gson.JsonParser; @Entity @Table(name="user_info") @NamedQueries({ - @NamedQuery(name="DefaultUserInfo.getAll", query = "select u from DefaultUserInfo u"), - @NamedQuery(name="DefaultUserInfo.getByUsername", query = "select u from DefaultUserInfo u WHERE u.preferredUsername = :username"), - @NamedQuery(name="DefaultUserInfo.getBySubject", query = "select u from DefaultUserInfo u WHERE u.sub = :sub") + @NamedQuery(name=DefaultUserInfo.QUERY_BY_USERNAME, query = "select u from DefaultUserInfo u WHERE u.preferredUsername = :" + DefaultUserInfo.PARAM_USERNAME), + @NamedQuery(name=DefaultUserInfo.QUERY_BY_EMAIL, query = "select u from DefaultUserInfo u WHERE u.email = :" + DefaultUserInfo.PARAM_EMAIL) }) public class DefaultUserInfo implements UserInfo { + public static final String QUERY_BY_USERNAME = "DefaultUserInfo.getByUsername"; + public static final String QUERY_BY_EMAIL = "DefaultUserInfo.getByEmailAddress"; + + public static final String PARAM_USERNAME = "username"; + public static final String PARAM_EMAIL = "email"; + + private static final long serialVersionUID = 6078310513185681918L; + private Long id; private String sub; private String preferredUsername; @@ -57,9 +74,10 @@ public class DefaultUserInfo implements UserInfo { private String locale; private String phoneNumber; private Boolean phoneNumberVerified; - private Address address; + private DefaultAddress address; private String updatedTime; private String birthdate; + private transient JsonObject src; // source JSON if this is loaded remotely /** @@ -353,7 +371,7 @@ public void setPhoneNumberVerified(Boolean phoneNumberVerified) { * @see org.mitre.openid.connect.model.UserInfo#getAddress() */ @Override - @OneToOne + @OneToOne(targetEntity = DefaultAddress.class, cascade = CascadeType.ALL) @JoinColumn(name="address_id") public Address getAddress() { return address; @@ -363,7 +381,11 @@ public Address getAddress() { */ @Override public void setAddress(Address address) { - this.address = address; + if (address != null) { + this.address = new DefaultAddress(address); + } else { + this.address = null; + } } /* (non-Javadoc) * @see org.mitre.openid.connect.model.UserInfo#getUpdatedTime() @@ -401,45 +423,51 @@ public void setBirthdate(String birthdate) { @Override public JsonObject toJson() { - JsonObject obj = new JsonObject(); - - obj.addProperty("sub", this.getSub()); - - obj.addProperty("name", this.getName()); - obj.addProperty("preferred_username", this.getPreferredUsername()); - obj.addProperty("given_name", this.getGivenName()); - obj.addProperty("family_name", this.getFamilyName()); - obj.addProperty("middle_name", this.getMiddleName()); - obj.addProperty("nickname", this.getNickname()); - obj.addProperty("profile", this.getProfile()); - obj.addProperty("picture", this.getPicture()); - obj.addProperty("website", this.getWebsite()); - obj.addProperty("gender", this.getGender()); - obj.addProperty("zone_info", this.getZoneinfo()); - obj.addProperty("locale", this.getLocale()); - obj.addProperty("updated_time", this.getUpdatedTime()); - obj.addProperty("birthdate", this.getBirthdate()); - - obj.addProperty("email", this.getEmail()); - obj.addProperty("email_verified", this.getEmailVerified()); - - obj.addProperty("phone_number", this.getPhoneNumber()); - obj.addProperty("phone_number_verified", this.getPhoneNumberVerified()); - - if (this.getAddress() != null) { - - JsonObject addr = new JsonObject(); - addr.addProperty("formatted", this.getAddress().getFormatted()); - addr.addProperty("street_address", this.getAddress().getStreetAddress()); - addr.addProperty("locality", this.getAddress().getLocality()); - addr.addProperty("region", this.getAddress().getRegion()); - addr.addProperty("postal_code", this.getAddress().getPostalCode()); - addr.addProperty("country", this.getAddress().getCountry()); - - obj.add("address", addr); + if (src == null) { + + JsonObject obj = new JsonObject(); + + obj.addProperty("sub", this.getSub()); + + obj.addProperty("name", this.getName()); + obj.addProperty("preferred_username", this.getPreferredUsername()); + obj.addProperty("given_name", this.getGivenName()); + obj.addProperty("family_name", this.getFamilyName()); + obj.addProperty("middle_name", this.getMiddleName()); + obj.addProperty("nickname", this.getNickname()); + obj.addProperty("profile", this.getProfile()); + obj.addProperty("picture", this.getPicture()); + obj.addProperty("website", this.getWebsite()); + obj.addProperty("gender", this.getGender()); + obj.addProperty("zoneinfo", this.getZoneinfo()); + obj.addProperty("locale", this.getLocale()); + obj.addProperty("updated_at", this.getUpdatedTime()); + obj.addProperty("birthdate", this.getBirthdate()); + + obj.addProperty("email", this.getEmail()); + obj.addProperty("email_verified", this.getEmailVerified()); + + obj.addProperty("phone_number", this.getPhoneNumber()); + obj.addProperty("phone_number_verified", this.getPhoneNumberVerified()); + + if (this.getAddress() != null) { + + JsonObject addr = new JsonObject(); + addr.addProperty("formatted", this.getAddress().getFormatted()); + addr.addProperty("street_address", this.getAddress().getStreetAddress()); + addr.addProperty("locality", this.getAddress().getLocality()); + addr.addProperty("region", this.getAddress().getRegion()); + addr.addProperty("postal_code", this.getAddress().getPostalCode()); + addr.addProperty("country", this.getAddress().getCountry()); + + obj.add("address", addr); + } + + return obj; + } else { + return src; } - return obj; } /** @@ -449,40 +477,41 @@ public JsonObject toJson() { */ public static UserInfo fromJson(JsonObject obj) { DefaultUserInfo ui = new DefaultUserInfo(); + ui.setSource(obj); + + ui.setSub(nullSafeGetString(obj, "sub")); + + ui.setName(nullSafeGetString(obj, "name")); + ui.setPreferredUsername(nullSafeGetString(obj, "preferred_username")); + ui.setGivenName(nullSafeGetString(obj, "given_name")); + ui.setFamilyName(nullSafeGetString(obj, "family_name")); + ui.setMiddleName(nullSafeGetString(obj, "middle_name")); + ui.setNickname(nullSafeGetString(obj, "nickname")); + ui.setProfile(nullSafeGetString(obj, "profile")); + ui.setPicture(nullSafeGetString(obj, "picture")); + ui.setWebsite(nullSafeGetString(obj, "website")); + ui.setGender(nullSafeGetString(obj, "gender")); + ui.setZoneinfo(nullSafeGetString(obj, "zoneinfo")); + ui.setLocale(nullSafeGetString(obj, "locale")); + ui.setUpdatedTime(nullSafeGetString(obj, "updated_at")); + ui.setBirthdate(nullSafeGetString(obj, "birthdate")); + + ui.setEmail(nullSafeGetString(obj, "email")); + ui.setEmailVerified(obj.has("email_verified") && obj.get("email_verified").isJsonPrimitive() ? obj.get("email_verified").getAsBoolean() : null); - ui.setSub(obj.has("sub") ? obj.get("sub").getAsString() : null); - - ui.setName(obj.has("name") ? obj.get("name").getAsString() : null); - ui.setPreferredUsername(obj.has("preferred_username") ? obj.get("preferred_username").getAsString() : null); - ui.setGivenName(obj.has("given_name") ? obj.get("given_name").getAsString() : null); - ui.setFamilyName(obj.has("family_name") ? obj.get("family_name").getAsString() : null); - ui.setMiddleName(obj.has("middle_name") ? obj.get("middle_name").getAsString() : null); - ui.setNickname(obj.has("nickname") ? obj.get("nickname").getAsString() : null); - ui.setProfile(obj.has("profile") ? obj.get("profile").getAsString() : null); - ui.setPicture(obj.has("picture") ? obj.get("picture").getAsString() : null); - ui.setWebsite(obj.has("website") ? obj.get("website").getAsString() : null); - ui.setGender(obj.has("gender") ? obj.get("gender").getAsString() : null); - ui.setZoneinfo(obj.has("zone_info") ? obj.get("zone_info").getAsString() : null); - ui.setLocale(obj.has("locale") ? obj.get("locale").getAsString() : null); - ui.setUpdatedTime(obj.has("updated_time") ? obj.get("updated_time").getAsString() : null); - ui.setBirthdate(obj.has("birthdate") ? obj.get("birthdate").getAsString() : null); - - ui.setEmail(obj.has("email") ? obj.get("email").getAsString() : null); - ui.setEmailVerified(obj.has("email_verified") ? obj.get("email_verified").getAsBoolean() : null); - - ui.setPhoneNumber(obj.has("phone_number") ? obj.get("phone_number").getAsString() : null); - ui.setPhoneNumberVerified(obj.has("phone_number_verified") ? obj.get("phone_number_verified").getAsBoolean() : null); + ui.setPhoneNumber(nullSafeGetString(obj, "phone_number")); + ui.setPhoneNumberVerified(obj.has("phone_number_verified") && obj.get("phone_number_verified").isJsonPrimitive() ? obj.get("phone_number_verified").getAsBoolean() : null); if (obj.has("address") && obj.get("address").isJsonObject()) { JsonObject addr = obj.get("address").getAsJsonObject(); - ui.setAddress(new Address()); + ui.setAddress(new DefaultAddress()); - ui.getAddress().setFormatted(addr.has("formatted") ? addr.get("formatted").getAsString() : null); - ui.getAddress().setStreetAddress(addr.has("street_address") ? addr.get("street_address").getAsString() : null); - ui.getAddress().setLocality(addr.has("locality") ? addr.get("locality").getAsString() : null); - ui.getAddress().setRegion(addr.has("region") ? addr.get("region").getAsString() : null); - ui.getAddress().setPostalCode(addr.has("postal_code") ? addr.get("postal_code").getAsString() : null); - ui.getAddress().setCountry(addr.has("country") ? addr.get("country").getAsString() : null); + ui.getAddress().setFormatted(nullSafeGetString(addr, "formatted")); + ui.getAddress().setStreetAddress(nullSafeGetString(addr, "street_address")); + ui.getAddress().setLocality(nullSafeGetString(addr, "locality")); + ui.getAddress().setRegion(nullSafeGetString(addr, "region")); + ui.getAddress().setPostalCode(nullSafeGetString(addr, "postal_code")); + ui.getAddress().setCountry(nullSafeGetString(addr, "country")); } @@ -490,6 +519,28 @@ public static UserInfo fromJson(JsonObject obj) { return ui; } + /** + * @return the jsonString + */ + @Override + @Basic + @Column(name = "src") + @Convert(converter = JsonObjectStringConverter.class) + public JsonObject getSource() { + return src; + } + + /** + * @param jsonString the jsonString to set + */ + public void setSource(JsonObject src) { + this.src = src; + } + + + private static String nullSafeGetString(JsonObject obj, String field) { + return obj.has(field) && obj.get(field).isJsonPrimitive() ? obj.get(field).getAsString() : null; + } /* (non-Javadoc) * @see java.lang.Object#hashCode() @@ -687,4 +738,25 @@ public boolean equals(Object obj) { return true; } + + /* + * Custom serialization to handle the JSON object + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + if (src == null) { + out.writeObject(null); + } else { + out.writeObject(src.toString()); + } + } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + Object o = in.readObject(); + if (o != null) { + JsonParser parser = new JsonParser(); + src = parser.parse((String)o).getAsJsonObject(); + } + } + } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java index 76e81547a3..b31b78045a 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/OIDCAuthenticationToken.java @@ -1,54 +1,58 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.model; -import java.util.ArrayList; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.ParseException; import java.util.Collection; -import org.mitre.openid.connect.config.ServerConfiguration; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import com.google.common.collect.ImmutableMap; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; /** - * + * * @author Michael Walsh, Justin Richer - * + * */ public class OIDCAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = 22100073066377804L; private final ImmutableMap principal; - private final String idTokenValue; // string representation of the id token private final String accessTokenValue; // string representation of the access token private final String refreshTokenValue; // string representation of the refresh token + private transient JWT idToken; // this needs a custom serializer private final String issuer; // issuer URL (parsed from the id token) private final String sub; // user id (parsed from the id token) - private final transient ServerConfiguration serverConfiguration; // server configuration used to fulfill this token, don't serialize it - private final transient UserInfo userInfo; // user info container, don't serialize it b/c it might be huge and can be re-fetched + private final UserInfo userInfo; // user info container /** * Constructs OIDCAuthenticationToken with a full set of authorities, marking this as authenticated. - * + * * Set to authenticated. - * + * * Constructs a Principal out of the subject and issuer. * @param subject * @param authorities @@ -57,7 +61,7 @@ public class OIDCAuthenticationToken extends AbstractAuthenticationToken { */ public OIDCAuthenticationToken(String subject, String issuer, UserInfo userInfo, Collection authorities, - String idTokenValue, String accessTokenValue, String refreshTokenValue) { + JWT idToken, String accessTokenValue, String refreshTokenValue) { super(authorities); @@ -65,48 +69,17 @@ public OIDCAuthenticationToken(String subject, String issuer, this.userInfo = userInfo; this.sub = subject; this.issuer = issuer; - this.idTokenValue = idTokenValue; + this.idToken = idToken; this.accessTokenValue = accessTokenValue; this.refreshTokenValue = refreshTokenValue; - this.serverConfiguration = null; // we don't need a server config anymore - setAuthenticated(true); } - /** - * Constructs OIDCAuthenticationToken for use as a data shuttle from the filter to the auth provider. - * - * Set to not-authenticated. - * - * Constructs a Principal out of the subject and issuer. - * @param sub - * @param idToken - */ - public OIDCAuthenticationToken(String subject, String issuer, - ServerConfiguration serverConfiguration, - String idTokenValue, String accessTokenValue, String refreshTokenValue) { - - super(new ArrayList(0)); - - this.principal = ImmutableMap.of("sub", subject, "iss", issuer); - this.sub = subject; - this.issuer = issuer; - this.idTokenValue = idTokenValue; - this.accessTokenValue = accessTokenValue; - this.refreshTokenValue = refreshTokenValue; - - this.userInfo = null; // we don't have a UserInfo yet - - this.serverConfiguration = serverConfiguration; - - - setAuthenticated(false); - } /* * (non-Javadoc) - * + * * @see org.springframework.security.core.Authentication#getCredentials() */ @Override @@ -129,8 +102,8 @@ public String getSub() { /** * @return the idTokenValue */ - public String getIdTokenValue() { - return idTokenValue; + public JWT getIdToken() { + return idToken; } /** @@ -147,13 +120,6 @@ public String getRefreshTokenValue() { return refreshTokenValue; } - /** - * @return the serverConfiguration - */ - public ServerConfiguration getServerConfiguration() { - return serverConfiguration; - } - /** * @return the issuer */ @@ -168,5 +134,23 @@ public UserInfo getUserInfo() { return userInfo; } + /* + * Custom serialization to handle the JSON object + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + if (idToken == null) { + out.writeObject(null); + } else { + out.writeObject(idToken.serialize()); + } + } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException, ParseException { + in.defaultReadObject(); + Object o = in.readObject(); + if (o != null) { + idToken = JWTParser.parse((String)o); + } + } } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java index 1828f0cf47..3ecf2fefe3 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PairwiseIdentifier.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.model; @@ -14,20 +31,26 @@ import javax.persistence.Table; /** - * + * * Holds the generated pairwise identifiers for a user. Can be tied to either a client ID or a sector identifier URL. - * + * * @author jricher * */ @Entity @Table(name = "pairwise_identifier") @NamedQueries({ - @NamedQuery(name="PairwiseIdentifier.getAll", query = "select p from PairwiseIdentifier p"), - @NamedQuery(name="PairwiseIdentifier.getBySectorIdentifier", query = "select p from PairwiseIdentifier p WHERE p.userSub = :sub AND p.sectorIdentifier = :sectorIdentifier") + @NamedQuery(name=PairwiseIdentifier.QUERY_ALL, query = "select p from PairwiseIdentifier p"), + @NamedQuery(name=PairwiseIdentifier.QUERY_BY_SECTOR_IDENTIFIER, query = "select p from PairwiseIdentifier p WHERE p.userSub = :" + PairwiseIdentifier.PARAM_SUB + " AND p.sectorIdentifier = :" + PairwiseIdentifier.PARAM_SECTOR_IDENTIFIER) }) public class PairwiseIdentifier { + public static final String QUERY_BY_SECTOR_IDENTIFIER = "PairwiseIdentifier.getBySectorIdentifier"; + public static final String QUERY_ALL = "PairwiseIdentifier.getAll"; + + public static final String PARAM_SECTOR_IDENTIFIER = "sectorIdentifier"; + public static final String PARAM_SUB = "sub"; + private Long id; private String identifier; private String userSub; @@ -70,7 +93,7 @@ public void setIdentifier(String identifier) { * @return the userSub */ @Basic - @Column(name = "sub") + @Column(name = PairwiseIdentifier.PARAM_SUB) public String getUserSub() { return userSub; } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PendingOIDCAuthenticationToken.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PendingOIDCAuthenticationToken.java new file mode 100644 index 0000000000..15201a8d4a --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/PendingOIDCAuthenticationToken.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.model; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.ParseException; +import java.util.ArrayList; + +import org.mitre.openid.connect.config.ServerConfiguration; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import com.google.common.collect.ImmutableMap; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTParser; + +/** + * AuthenticationToken for use as a data shuttle from the filter to the auth provider. + * + * @author jricher + * + */ +public class PendingOIDCAuthenticationToken extends AbstractAuthenticationToken { + + private static final long serialVersionUID = 22100073066377804L; + + private final ImmutableMap principal; + private final String accessTokenValue; // string representation of the access token + private final String refreshTokenValue; // string representation of the refresh token + private transient JWT idToken; // this needs a custom serializer + private final String issuer; // issuer URL (parsed from the id token) + private final String sub; // user id (parsed from the id token) + + private final transient ServerConfiguration serverConfiguration; // server configuration used to fulfill this token, don't serialize it + + /** + * Constructs OIDCAuthenticationToken for use as a data shuttle from the filter to the auth provider. + * + * Set to not-authenticated. + * + * Constructs a Principal out of the subject and issuer. + * @param sub + * @param idToken + */ + public PendingOIDCAuthenticationToken (String subject, String issuer, + ServerConfiguration serverConfiguration, + JWT idToken, String accessTokenValue, String refreshTokenValue) { + + super(new ArrayList(0)); + + this.principal = ImmutableMap.of("sub", subject, "iss", issuer); + this.sub = subject; + this.issuer = issuer; + this.idToken = idToken; + this.accessTokenValue = accessTokenValue; + this.refreshTokenValue = refreshTokenValue; + + this.serverConfiguration = serverConfiguration; + + + setAuthenticated(false); + } + + /* + * (non-Javadoc) + * + * @see org.springframework.security.core.Authentication#getCredentials() + */ + @Override + public Object getCredentials() { + return accessTokenValue; + } + + /** + * Get the principal of this object, an immutable map of the subject and issuer. + */ + @Override + public Object getPrincipal() { + return principal; + } + + public String getSub() { + return sub; + } + + /** + * @return the idTokenValue + */ + public JWT getIdToken() { + return idToken; + } + + /** + * @return the accessTokenValue + */ + public String getAccessTokenValue() { + return accessTokenValue; + } + + /** + * @return the refreshTokenValue + */ + public String getRefreshTokenValue() { + return refreshTokenValue; + } + + /** + * @return the serverConfiguration + */ + public ServerConfiguration getServerConfiguration() { + return serverConfiguration; + } + + /** + * @return the issuer + */ + public String getIssuer() { + return issuer; + } + + /* + * Custom serialization to handle the JSON object + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + if (idToken == null) { + out.writeObject(null); + } else { + out.writeObject(idToken.serialize()); + } + } + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException, ParseException { + in.defaultReadObject(); + Object o = in.readObject(); + if (o != null) { + idToken = JWTParser.parse((String)o); + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/UserInfo.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/UserInfo.java index ce18a53033..2fbac5e402 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/UserInfo.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/UserInfo.java @@ -1,235 +1,244 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.model; +import java.io.Serializable; + import com.google.gson.JsonObject; -public interface UserInfo { +public interface UserInfo extends Serializable { /** * @return the userId */ - public abstract String getSub(); + public String getSub(); /** * @param sub the userId to set */ - public abstract void setSub(String sub); + public void setSub(String sub); /** * @return the preferred username */ - public abstract String getPreferredUsername(); + public String getPreferredUsername(); /** * @param preferredUsername the preferredUsername to set */ - public abstract void setPreferredUsername(String preferredUsername); + public void setPreferredUsername(String preferredUsername); /** * @return the name */ - public abstract String getName(); + public String getName(); /** * @param name the name to set */ - public abstract void setName(String name); + public void setName(String name); /** * @return the givenName */ - public abstract String getGivenName(); + public String getGivenName(); /** * @param givenName the givenName to set */ - public abstract void setGivenName(String givenName); + public void setGivenName(String givenName); /** * @return the familyName */ - public abstract String getFamilyName(); + public String getFamilyName(); /** * @param familyName the familyName to set */ - public abstract void setFamilyName(String familyName); + public void setFamilyName(String familyName); /** * @return the middleName */ - public abstract String getMiddleName(); + public String getMiddleName(); /** * @param middleName the middleName to set */ - public abstract void setMiddleName(String middleName); + public void setMiddleName(String middleName); /** * @return the nickname */ - public abstract String getNickname(); + public String getNickname(); /** * @param nickname the nickname to set */ - public abstract void setNickname(String nickname); + public void setNickname(String nickname); /** * @return the profile */ - public abstract String getProfile(); + public String getProfile(); /** * @param profile the profile to set */ - public abstract void setProfile(String profile); + public void setProfile(String profile); /** * @return the picture */ - public abstract String getPicture(); + public String getPicture(); /** * @param picture the picture to set */ - public abstract void setPicture(String picture); + public void setPicture(String picture); /** * @return the website */ - public abstract String getWebsite(); + public String getWebsite(); /** * @param website the website to set */ - public abstract void setWebsite(String website); + public void setWebsite(String website); /** * @return the email */ - public abstract String getEmail(); + public String getEmail(); /** * @param email the email to set */ - public abstract void setEmail(String email); + public void setEmail(String email); /** * @return the verified */ - public abstract Boolean getEmailVerified(); + public Boolean getEmailVerified(); /** * @param verified the verified to set */ - public abstract void setEmailVerified(Boolean emailVerified); + public void setEmailVerified(Boolean emailVerified); /** * @return the gender */ - public abstract String getGender(); + public String getGender(); /** * @param gender the gender to set */ - public abstract void setGender(String gender); + public void setGender(String gender); /** * @return the zoneinfo */ - public abstract String getZoneinfo(); + public String getZoneinfo(); /** * @param zoneinfo the zoneinfo to set */ - public abstract void setZoneinfo(String zoneinfo); + public void setZoneinfo(String zoneinfo); /** * @return the locale */ - public abstract String getLocale(); + public String getLocale(); /** * @param locale the locale to set */ - public abstract void setLocale(String locale); + public void setLocale(String locale); /** * @return the phoneNumber */ - public abstract String getPhoneNumber(); + public String getPhoneNumber(); /** * @param phoneNumber the phoneNumber to set */ - public abstract void setPhoneNumber(String phoneNumber); + public void setPhoneNumber(String phoneNumber); /** - * + * */ - public abstract Boolean getPhoneNumberVerified(); + public Boolean getPhoneNumberVerified(); /** - * + * * @param phoneNumberVerified */ - public abstract void setPhoneNumberVerified(Boolean phoneNumberVerified); + public void setPhoneNumberVerified(Boolean phoneNumberVerified); /** * @return the address */ - public abstract Address getAddress(); + public Address getAddress(); /** * @param address the address to set */ - public abstract void setAddress(Address address); + public void setAddress(Address address); /** * @return the updatedTime */ - public abstract String getUpdatedTime(); + public String getUpdatedTime(); /** * @param updatedTime the updatedTime to set */ - public abstract void setUpdatedTime(String updatedTime); + public void setUpdatedTime(String updatedTime); /** - * + * * @return */ - public abstract String getBirthdate(); + public String getBirthdate(); /** - * + * * @param birthdate */ - public abstract void setBirthdate(String birthdate); + public void setBirthdate(String birthdate); + + /** + * Serialize this UserInfo object to JSON. + * + * @return + */ + public JsonObject toJson(); /** - * Serialize this UserInfo object to JSON - * + * The JSON source of this UserInfo (if it was fetched), or null if it's local. * @return */ - public abstract JsonObject toJson(); + public JsonObject getSource(); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java index b8ecafc48a..c3e58db0d3 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/WhitelistedSite.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.model; import java.util.Set; @@ -41,12 +42,19 @@ @Entity @Table(name="whitelisted_site") @NamedQueries({ - @NamedQuery(name = "WhitelistedSite.getAll", query = "select w from WhitelistedSite w"), - @NamedQuery(name = "WhitelistedSite.getByClientId", query = "select w from WhitelistedSite w where w.clientId = :clientId"), - @NamedQuery(name = "WhitelistedSite.getByCreatoruserId", query = "select w from WhitelistedSite w where w.creatorUserId = :userId") + @NamedQuery(name = WhitelistedSite.QUERY_ALL, query = "select w from WhitelistedSite w"), + @NamedQuery(name = WhitelistedSite.QUERY_BY_CLIENT_ID, query = "select w from WhitelistedSite w where w.clientId = :" + WhitelistedSite.PARAM_CLIENT_ID), + @NamedQuery(name = WhitelistedSite.QUERY_BY_CREATOR, query = "select w from WhitelistedSite w where w.creatorUserId = :" + WhitelistedSite.PARAM_USER_ID) }) public class WhitelistedSite { + public static final String QUERY_BY_CREATOR = "WhitelistedSite.getByCreatoruserId"; + public static final String QUERY_BY_CLIENT_ID = "WhitelistedSite.getByClientId"; + public static final String QUERY_ALL = "WhitelistedSite.getAll"; + + public static final String PARAM_USER_ID = "userId"; + public static final String PARAM_CLIENT_ID = "clientId"; + // unique id private Long id; diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/model/convert/JsonObjectStringConverter.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/convert/JsonObjectStringConverter.java new file mode 100644 index 0000000000..78c33e8cdd --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/model/convert/JsonObjectStringConverter.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import com.google.common.base.Strings; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * @author jricher + * + */ +@Converter +public class JsonObjectStringConverter implements AttributeConverter { + + private JsonParser parser = new JsonParser(); + + @Override + public String convertToDatabaseColumn(JsonObject attribute) { + if (attribute != null) { + return attribute.toString(); + } else { + return null; + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public JsonObject convertToEntityAttribute(String dbData) { + if (!Strings.isNullOrEmpty(dbData)) { + return parser.parse(dbData).getAsJsonObject(); + } else { + return null; + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java index 290a9ed4ba..4482b38234 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/AddressRepository.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.repository; import org.mitre.openid.connect.model.Address; @@ -28,35 +29,11 @@ public interface AddressRepository { /** * Returns the Address for the given id - * + * * @param id * id the id of the Address * @return a valid Address if it exists, null otherwise */ public Address getById(Long id); - /** - * Removes the given Address from the repository - * - * @param address - * the Address object to remove - */ - public void remove(Address address); - - /** - * Removes an Address from the repository - * - * @param id - * the id of the Address to remove - */ - public void removeById(Long id); - - /** - * Persists a Address - * - * @param address - * the Address to be saved - * @return - */ - public Address save(Address address); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java index a2633a145c..21e06d97e9 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/ApprovedSiteRepository.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.repository; import java.util.Collection; @@ -30,7 +31,7 @@ public interface ApprovedSiteRepository { /** * Returns the ApprovedSite for the given id - * + * * @param id * id the id of the ApprovedSite * @return a valid ApprovedSite if it exists, null otherwise @@ -39,7 +40,7 @@ public interface ApprovedSiteRepository { /** * Return a collection of all ApprovedSites managed by this repository - * + * * @return the ApprovedSite collection, or null */ public Collection getAll(); @@ -47,7 +48,7 @@ public interface ApprovedSiteRepository { /** * Return a collection of ApprovedSite managed by this repository matching the * provided client ID and user ID - * + * * @param clientId * @param userId * @return @@ -56,7 +57,7 @@ public interface ApprovedSiteRepository { /** * Removes the given ApprovedSite from the repository - * + * * @param aggregator * the ApprovedSite object to remove */ @@ -64,7 +65,7 @@ public interface ApprovedSiteRepository { /** * Persists an ApprovedSite - * + * * @param aggregator * valid ApprovedSite instance * @return the persisted entity diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java index 335439a25c..9c491c3900 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/BlacklistedSiteRepository.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.repository; diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java index f5f0423deb..b17850b459 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/PairwiseIdentifierRepository.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.repository; @@ -13,7 +30,7 @@ public interface PairwiseIdentifierRepository { /** * Get a pairwise identifier by its associated user subject and sector identifier. - * + * * @param sub * @param sectorIdentifierUri * @return @@ -22,7 +39,7 @@ public interface PairwiseIdentifierRepository { /** * Save a pairwise identifier to the database. - * + * * @param pairwise */ public void save(PairwiseIdentifier pairwise); diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java index 115ad25aff..9763ca14a5 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/UserInfoRepository.java @@ -1,70 +1,46 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.repository; -import java.util.Collection; - import org.mitre.openid.connect.model.UserInfo; /** * UserInfo repository interface - * + * * @author Michael Joseph Walsh * */ public interface UserInfoRepository { /** - * Returns the UserInfo for the given subject - * - * @param sub - * the subject of the UserInfo - * @return a valid UserInfo if it exists, null otherwise - */ - public UserInfo getBySubject(String sub); - - /** - * Persists a UserInfo - * - * @param user + * Get a UserInfo object by its preferred_username field + * @param username * @return */ - public UserInfo save(UserInfo userInfo); - - /** - * Removes the given UserInfo from the repository - * - * @param userInfo - * the UserInfo object to remove - */ - public void remove(UserInfo userInfo); - - /** - * Return a collection of all UserInfos managed by this repository - * - * @return the UserInfo collection, or null - */ - public Collection getAll(); + public UserInfo getByUsername(String username); /** - * Get a UserInfo object by its preferred_username field - * @param username + * + * Get the UserInfo object by its email field + * + * @param email * @return */ - public UserInfo getByUsername(String username); + public UserInfo getByEmailAddress(String email); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java index 3cc2550e7e..b46ec5d273 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/repository/WhitelistedSiteRepository.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.repository; import java.util.Collection; @@ -22,22 +23,22 @@ /** * WhitelistedSite repository interface - * + * * @author Michael Joseph Walsh, aanganes - * + * */ public interface WhitelistedSiteRepository { /** * Return a collection of all WhitelistedSite managed by this repository - * + * * @return the WhitelistedSite collection, or null */ public Collection getAll(); /** * Returns the WhitelistedSite for the given id - * + * * @param id * id the id of the WhitelistedSite * @return a valid WhitelistedSite if it exists, null otherwise @@ -46,7 +47,7 @@ public interface WhitelistedSiteRepository { /** * Find a WhitelistedSite by its associated ClientDetails reference - * + * * @param client the Relying Party * @return the corresponding WhitelistedSite if one exists for the RP, or null */ @@ -54,7 +55,7 @@ public interface WhitelistedSiteRepository { /** * Return a collection of the WhitelistedSites created by a given user - * + * * @param creator the id of the admin who may have created some WhitelistedSites * @return the collection of corresponding WhitelistedSites, if any, or null */ @@ -62,7 +63,7 @@ public interface WhitelistedSiteRepository { /** * Removes the given IdToken from the repository - * + * * @param whitelistedSite * the WhitelistedSite object to remove */ @@ -70,7 +71,7 @@ public interface WhitelistedSiteRepository { /** * Persists a WhitelistedSite - * + * * @param whitelistedSite * @return */ diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java index 4f8270bfaa..bf033d8874 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ApprovedSiteService.java @@ -1,43 +1,45 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.service; import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Set; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.openid.connect.model.ApprovedSite; -import org.mitre.openid.connect.model.WhitelistedSite; import org.springframework.security.oauth2.provider.ClientDetails; /** * Interface for ApprovedSite service - * + * * @author Michael Joseph Walsh, aanganes - * + * */ public interface ApprovedSiteService { - public ApprovedSite createApprovedSite(String clientId, String userId, Date timeoutDate, Set allowedScopes, WhitelistedSite whitelistedSite); + public ApprovedSite createApprovedSite(String clientId, String userId, Date timeoutDate, Set allowedScopes); /** * Return a collection of all ApprovedSites - * + * * @return the ApprovedSite collection, or null */ public Collection getAll(); @@ -45,7 +47,7 @@ public interface ApprovedSiteService { /** * Return a collection of ApprovedSite managed by this repository matching the * provided client ID and user ID - * + * * @param clientId * @param userId * @return @@ -54,7 +56,7 @@ public interface ApprovedSiteService { /** * Save an ApprovedSite - * + * * @param approvedSite * the ApprovedSite to be saved */ @@ -62,7 +64,7 @@ public interface ApprovedSiteService { /** * Get ApprovedSite for id - * + * * @param id * id for ApprovedSite * @return ApprovedSite for id, or null @@ -71,7 +73,7 @@ public interface ApprovedSiteService { /** * Remove the ApprovedSite - * + * * @param approvedSite * the ApprovedSite to remove */ @@ -102,4 +104,11 @@ public interface ApprovedSiteService { * @return */ public void clearExpiredSites(); + + /** + * Return all approved access tokens for the site. + * @return + */ + public List getApprovedAccessTokens(ApprovedSite approvedSite); + } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java index 529f429e70..88ef7bff7d 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/BlacklistedSiteService.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ClientLogoLoadingService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ClientLogoLoadingService.java new file mode 100644 index 0000000000..92cfd67eca --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ClientLogoLoadingService.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.service; + +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.openid.connect.model.CachedImage; + +/** + * @author jricher + * + */ +public interface ClientLogoLoadingService { + + /** + * @param client + * @return + */ + public CachedImage getLogo(ClientDetailsEntity client); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/LoginHintExtracter.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/LoginHintExtracter.java new file mode 100644 index 0000000000..f83894bf29 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/LoginHintExtracter.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.service; + +/** + * @author jricher + * + */ +public interface LoginHintExtracter { + + /** + * @param loginHint + * @return + */ + public String extractHint(String loginHint); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataService.java new file mode 100644 index 0000000000..ce85762799 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataService.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.openid.connect.service; + +import java.io.IOException; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * @author jricher + * @author arielak + */ +public interface MITREidDataService { + + /** + * Data member for 1.X configurations + */ + public static final String MITREID_CONNECT_1_0 = "mitreid-connect-1.0"; + public static final String MITREID_CONNECT_1_1 = "mitreid-connect-1.1"; + public static final String MITREID_CONNECT_1_2 = "mitreid-connect-1.2"; + public static final String MITREID_CONNECT_1_3 = "mitreid-connect-1.3"; + + // member names + public static final String REFRESHTOKENS = "refreshTokens"; + public static final String ACCESSTOKENS = "accessTokens"; + public static final String WHITELISTEDSITES = "whitelistedSites"; + public static final String BLACKLISTEDSITES = "blacklistedSites"; + public static final String AUTHENTICATIONHOLDERS = "authenticationHolders"; + public static final String GRANTS = "grants"; + public static final String CLIENTS = "clients"; + public static final String SYSTEMSCOPES = "systemScopes"; + + /** + * Write out the current server state to the given JSON writer as a JSON object + * + * @param writer + * @throws IOException + */ + void exportData(JsonWriter writer) throws IOException; + + /** + * Read in the current server state from the given JSON reader as a JSON object + * + * @param reader + */ + void importData(JsonReader reader) throws IOException; + + /** + * Return true if the this data service supports the given version. This is called before + * handing the service the reader through its importData function. + * + * @param version + * @return + */ + boolean supportsVersion(String version); + +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataServiceExtension.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataServiceExtension.java new file mode 100644 index 0000000000..b42dafe9e9 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataServiceExtension.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.service; + +import java.io.IOException; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +/** + * A modular extension to the data import/export layer. Any instances of this need to be + * declared as beans to be picked up by the data services. + * + * @author jricher + * + */ +public interface MITREidDataServiceExtension { + + /** + * Export any data for this extension. This is called from the top level object. + * All extensions MUST return the writer to a state such that another member of + * the top level object can be written next. + * + * @param writer + */ + void exportExtensionData(JsonWriter writer) throws IOException; + + /** + * Import data that's part of this extension. This is called from the context of + * reading the top level object. All extensions MUST return the reader to a state + * such that another member of the top level object can be read next. The name of + * the data element being imported is passed in as name. If the extension does not + * support this data element, it must return without advancing the reader. + * + * Returns "true" if the item was processed, "false" otherwise. + * + * @param reader + */ + boolean importExtensionData(String name, JsonReader reader) throws IOException; + + /** + * Signal the extension to wrap up all object processing and finalize its + */ + void fixExtensionObjectReferences(MITREidDataServiceMaps maps); + + /** + * Return + * @param mitreidConnect13 + * @return + */ + boolean supportsVersion(String version); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataServiceMaps.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataServiceMaps.java new file mode 100644 index 0000000000..38e5fb46a9 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/MITREidDataServiceMaps.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.openid.connect.service; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author jricher + * + */ +public class MITREidDataServiceMaps { + + private Map accessTokenOldToNewIdMap = new HashMap(); + private Map accessTokenToAuthHolderRefs = new HashMap(); + private Map accessTokenToClientRefs = new HashMap(); + private Map accessTokenToRefreshTokenRefs = new HashMap(); + private Map authHolderOldToNewIdMap = new HashMap(); + private Map grantOldToNewIdMap = new HashMap<>(); + private Map> grantToAccessTokensRefs = new HashMap<>(); + private Map refreshTokenOldToNewIdMap = new HashMap(); + private Map refreshTokenToAuthHolderRefs = new HashMap(); + private Map refreshTokenToClientRefs = new HashMap(); + private Map whitelistedSiteOldToNewIdMap = new HashMap(); + /** + * @return the accessTokenOldToNewIdMap + */ + public Map getAccessTokenOldToNewIdMap() { + return accessTokenOldToNewIdMap; + } + /** + * @return the accessTokenToAuthHolderRefs + */ + public Map getAccessTokenToAuthHolderRefs() { + return accessTokenToAuthHolderRefs; + } + /** + * @return the accessTokenToClientRefs + */ + public Map getAccessTokenToClientRefs() { + return accessTokenToClientRefs; + } + /** + * @return the accessTokenToRefreshTokenRefs + */ + public Map getAccessTokenToRefreshTokenRefs() { + return accessTokenToRefreshTokenRefs; + } + /** + * @return the authHolderOldToNewIdMap + */ + public Map getAuthHolderOldToNewIdMap() { + return authHolderOldToNewIdMap; + } + /** + * @return the grantOldToNewIdMap + */ + public Map getGrantOldToNewIdMap() { + return grantOldToNewIdMap; + } + /** + * @return the grantToAccessTokensRefs + */ + public Map> getGrantToAccessTokensRefs() { + return grantToAccessTokensRefs; + } + /** + * @return the refreshTokenOldToNewIdMap + */ + public Map getRefreshTokenOldToNewIdMap() { + return refreshTokenOldToNewIdMap; + } + /** + * @return the refreshTokenToAuthHolderRefs + */ + public Map getRefreshTokenToAuthHolderRefs() { + return refreshTokenToAuthHolderRefs; + } + /** + * @return the refreshTokenToClientRefs + */ + public Map getRefreshTokenToClientRefs() { + return refreshTokenToClientRefs; + } + /** + * @return the whitelistedSiteOldToNewIdMap + */ + public Map getWhitelistedSiteOldToNewIdMap() { + return whitelistedSiteOldToNewIdMap; + } + + public void clearAll() { + refreshTokenToClientRefs.clear(); + refreshTokenToAuthHolderRefs.clear(); + accessTokenToClientRefs.clear(); + accessTokenToAuthHolderRefs.clear(); + accessTokenToRefreshTokenRefs.clear(); + refreshTokenOldToNewIdMap.clear(); + accessTokenOldToNewIdMap.clear(); + grantOldToNewIdMap.clear(); + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java index 332340f0bb..146f6164e4 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.service; import java.util.Date; @@ -22,11 +23,11 @@ import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.security.oauth2.provider.OAuth2Request; -import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jwt.JWT; /** * Service to create specialty OpenID Connect tokens. - * + * * @author Amanda Anganes * */ @@ -34,7 +35,7 @@ public interface OIDCTokenService { /** * Create an id token with the information provided. - * + * * @param client * @param request * @param issueTime @@ -43,17 +44,31 @@ public interface OIDCTokenService { * @param accessToken * @return */ - public OAuth2AccessTokenEntity createIdToken( + public JWT createIdToken( ClientDetailsEntity client, OAuth2Request request, Date issueTime, - String sub, JWSAlgorithm signingAlg, - OAuth2AccessTokenEntity accessToken); + String sub, OAuth2AccessTokenEntity accessToken); /** * Create a registration access token for the given client. - * + * * @param client * @return */ public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client); + /** + * Create a resource access token for the given client (protected resource). + * + * @param client + * @return + */ + public OAuth2AccessTokenEntity createResourceAccessToken(ClientDetailsEntity client); + + /** + * Rotate the registration or resource token for a client + * @param client + * @return + */ + public OAuth2AccessTokenEntity rotateRegistrationAccessTokenForClient(ClientDetailsEntity client); + } \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java index 59551f71f8..06f437812e 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/PairwiseIdentiferService.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; @@ -14,9 +31,9 @@ public interface PairwiseIdentiferService { /** * Calcualtes the pairwise identifier for the given userinfo object and client. - * + * * Returns 'null' if no identifer could be calculated. - * + * * @param userInfo * @param client * @return diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java index 36946bff77..d07d888e0f 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/ScopeClaimTranslationService.java @@ -1,5 +1,22 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; @@ -15,6 +32,4 @@ public interface ScopeClaimTranslationService { public Set getClaimsForScopeSet(Set scopes); - public String getFieldNameForClaim(String claim); - -} \ No newline at end of file +} diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/StatsService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/StatsService.java index 8bf56bbb21..007e3e76c6 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/StatsService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/StatsService.java @@ -1,26 +1,29 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; import java.util.Map; +import org.mitre.openid.connect.model.ClientStat; + /** * @author jricher * @@ -32,24 +35,22 @@ public interface StatsService { * approvalCount: total approved sites * userCount: unique users * clientCount: unique clients - * + * * @return */ - public Map calculateSummaryStats(); + public Map getSummaryStats(); /** - * Calculate usage count for all clients - * - * @return a map of id of client object to number of approvals + * Calculate the usage count for a single client + * + * @param clientId the id of the client to search on + * @return */ - public Map calculateByClientId(); + public ClientStat getCountForClientId(String clientId); /** - * Calculate the usage count for a single client - * - * @param id the id of the client to search on - * @return + * Trigger the stats to be recalculated upon next update. */ - public Integer countForClientId(Long id); + public void resetCache(); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java index f43379ff6e..4a36c236ca 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/UserInfoService.java @@ -1,57 +1,32 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.service; import org.mitre.openid.connect.model.UserInfo; /** * Interface for UserInfo service - * + * * @author Michael Joseph Walsh - * + * */ public interface UserInfoService { - /** - * Save an UserInfo - * - * @param userInfo - * the UserInfo to be saved - */ - public void save(UserInfo userInfo); - - /** - * Get UserInfo for the Subject - * - * @param sub - * subject for UserInfo - * @return UserInfo for sub, or null - */ - public UserInfo getBySubject(String userId); - - /** - * Remove the UserInfo - * - * @param userInfo - * the UserInfo to remove - */ - public void remove(UserInfo userInfo); - - /** * Get the UserInfo for the given username (usually maps to the * preferredUsername field). @@ -70,4 +45,12 @@ public interface UserInfoService { */ public UserInfo getByUsernameAndClientId(String username, String clientId); + /** + * Get the user registered at this server with the given email address. + * + * @param email + * @return + */ + public UserInfo getByEmailAddress(String email); + } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java index dce92c0e74..c4780ef87c 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/WhitelistedSiteService.java @@ -1,19 +1,20 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.openid.connect.service; import java.util.Collection; @@ -22,22 +23,22 @@ /** * Interface for WhitelistedSite service - * + * * @author Michael Joseph Walsh, aanganes - * + * */ public interface WhitelistedSiteService { /** * Return a collection of all WhitelistedSite managed by this service - * + * * @return the WhitelistedSite collection, or null */ public Collection getAll(); /** * Returns the WhitelistedSite for the given id - * + * * @param id * id the id of the WhitelistedSite * @return a valid WhitelistedSite if it exists, null otherwise @@ -46,23 +47,17 @@ public interface WhitelistedSiteService { /** * Find a WhitelistedSite by its associated ClientDetails reference - * + * * @param client the Relying Party * @return the corresponding WhitelistedSite if one exists for the RP, or null */ public WhitelistedSite getByClientId(String clientId); - /** - * Return a collection of the WhitelistedSites created by a given user - * - * @param creator the user id of an admin who may have made some WhitelistedSites - * @return the collection of corresponding WhitelistedSites, if any, or null - */ - public Collection getByCreator(String creatorId); + /** * Removes the given WhitelistedSite from the repository - * + * * @param address * the WhitelistedSite object to remove */ @@ -70,7 +65,7 @@ public interface WhitelistedSiteService { /** * Persists a new WhitelistedSite - * + * * @param whitelistedSite * the WhitelistedSite to be saved * @return diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JWKSetView.java similarity index 72% rename from openid-connect-common/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java rename to openid-connect-common/src/main/java/org/mitre/openid/connect/view/JWKSetView.java index b07e765b6f..f18deaee18 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JwkKeyListView.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JWKSetView.java @@ -1,21 +1,22 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -29,6 +30,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.servlet.view.AbstractView; @@ -39,21 +41,25 @@ * @author jricher * */ -@Component("jwkKeyList") -public class JwkKeyListView extends AbstractView { +@Component(JWKSetView.VIEWNAME) +public class JWKSetView extends AbstractView { - private static Logger logger = LoggerFactory.getLogger(JwkKeyListView.class); + public static final String VIEWNAME = "jwkSet"; + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(JWKSetView.class); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { - response.setContentType("application/json"); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); //BiMap keyMap = (BiMap) model.get("keys"); Map keys = (Map) model.get("keys"); - JWKSet jwkSet = new JWKSet(new ArrayList(keys.values())); + JWKSet jwkSet = new JWKSet(new ArrayList<>(keys.values())); try { @@ -62,7 +68,7 @@ protected void renderMergedOutputModel(Map model, HttpServletReq } catch (IOException e) { - logger.error("IOException in JwkKeyListView.java: ", e); + logger.error("IOException in JWKSetView.java: ", e); } diff --git a/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java index 686abe4ffc..ac7ab41070 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/web/UserInfoInterceptor.java @@ -1,26 +1,26 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; import java.lang.reflect.Type; -import java.security.Principal; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -29,11 +29,12 @@ import org.mitre.openid.connect.model.UserInfo; import org.mitre.openid.connect.service.UserInfoService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationTrustResolver; +import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.web.servlet.ModelAndView; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; -import org.springframework.web.servlet.view.RedirectView; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -44,65 +45,63 @@ /** * Injects the UserInfo object for the current user into the current model's context, if both exist. Allows JSPs and the like to call "userInfo.name" and other fields. - * + * * @author jricher * */ public class UserInfoInterceptor extends HandlerInterceptorAdapter { private Gson gson = new GsonBuilder() - .registerTypeHierarchyAdapter(GrantedAuthority.class, new JsonSerializer() { - @Override - public JsonElement serialize(GrantedAuthority src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.getAuthority()); - } - }) - .create(); + .registerTypeHierarchyAdapter(GrantedAuthority.class, new JsonSerializer() { + @Override + public JsonElement serialize(GrantedAuthority src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getAuthority()); + } + }) + .create(); - @Autowired + @Autowired (required = false) private UserInfoService userInfoService; - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { + private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); - if (modelAndView != null && !modelAndView.getModel().containsKey("userInfo")) { // skip checking at all if we have no model and view to hand the user to - // or if there's already a userInfo object in there - - // TODO: this is a patch to get around a potential information leak from #492 - if (!(modelAndView.getView() instanceof RedirectView)) { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - // get our principal from the security context - Principal p = request.getUserPrincipal(); + Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (p instanceof Authentication && !modelAndView.getModel().containsKey("userAuthorities")){ - Authentication auth = (Authentication)p; - modelAndView.addObject("userAuthorities", gson.toJson(auth.getAuthorities())); - } + if (auth instanceof Authentication){ + request.setAttribute("userAuthorities", gson.toJson(auth.getAuthorities())); + } - if (p instanceof OIDCAuthenticationToken) { - // if they're logging into this server from a remote OIDC server, pass through their user info - OIDCAuthenticationToken oidc = (OIDCAuthenticationToken) p; - modelAndView.addObject("userInfo", oidc.getUserInfo()); - modelAndView.addObject("userInfoJson", oidc.getUserInfo().toJson()); + if (!trustResolver.isAnonymous(auth)) { // skip lookup on anonymous logins + if (auth instanceof OIDCAuthenticationToken) { + // if they're logging into this server from a remote OIDC server, pass through their user info + OIDCAuthenticationToken oidc = (OIDCAuthenticationToken) auth; + if (oidc.getUserInfo() != null) { + request.setAttribute("userInfo", oidc.getUserInfo()); + request.setAttribute("userInfoJson", oidc.getUserInfo().toJson()); } else { - if (p != null && p.getName() != null) { // don't bother checking if we don't have a principal + request.setAttribute("userInfo", null); + request.setAttribute("userInfoJson", "null"); + } + } else { + // don't bother checking if we don't have a principal or a userInfoService to work with + if (auth != null && auth.getName() != null && userInfoService != null) { - // try to look up a user based on the principal's name - UserInfo user = userInfoService.getByUsername(p.getName()); + // try to look up a user based on the principal's name + UserInfo user = userInfoService.getByUsername(auth.getName()); - // if we have one, inject it so views can use it - if (user != null) { - modelAndView.addObject("userInfo", user); - modelAndView.addObject("userInfoJson", user.toJson()); - } + // if we have one, inject it so views can use it + if (user != null) { + request.setAttribute("userInfo", user); + request.setAttribute("userInfoJson", user.toJson()); } } } } + return true; } - - - } diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/Claim.java b/openid-connect-common/src/main/java/org/mitre/uma/model/Claim.java new file mode 100644 index 0000000000..d93e99c9c7 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/Claim.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model; + +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.Table; + +import org.mitre.oauth2.model.convert.JsonElementStringConverter; + +import com.google.gson.JsonElement; + +/** + * @author jricher + * + */ +@Entity +@Table(name = "claim") +public class Claim { + + private Long id; + private String name; + private String friendlyName; + private String claimType; + private JsonElement value; + private Set claimTokenFormat; + private Set issuer; + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + /** + * @return the name + */ + @Basic + @Column(name = "name") + public String getName() { + return name; + } + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the friendlyName + */ + @Basic + @Column(name = "friendly_name") + public String getFriendlyName() { + return friendlyName; + } + /** + * @param friendlyName the friendlyName to set + */ + public void setFriendlyName(String friendlyName) { + this.friendlyName = friendlyName; + } + + /** + * @return the claimType + */ + @Basic + @Column(name = "claim_type") + public String getClaimType() { + return claimType; + } + /** + * @param claimType the claimType to set + */ + public void setClaimType(String claimType) { + this.claimType = claimType; + } + + /** + * @return the claimTokenFormat + */ + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "claim_token_format") + @CollectionTable( + name = "claim_token_format", + joinColumns = @JoinColumn(name = "owner_id") + ) + public Set getClaimTokenFormat() { + return claimTokenFormat; + } + /** + * @param claimTokenFormat the claimTokenFormat to set + */ + public void setClaimTokenFormat(Set claimTokenFormat) { + this.claimTokenFormat = claimTokenFormat; + } + + /** + * @return the issuer + */ + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "issuer") + @CollectionTable( + name = "claim_issuer", + joinColumns = @JoinColumn(name = "owner_id") + ) + public Set getIssuer() { + return issuer; + } + /** + * @param issuer the issuer to set + */ + public void setIssuer(Set issuer) { + this.issuer = issuer; + } + + /** + * @return the value + */ + @Basic + @Column(name = "claim_value") + @Convert(converter = JsonElementStringConverter.class) + public JsonElement getValue() { + return value; + } + /** + * @param value the value to set + */ + public void setValue(JsonElement value) { + this.value = value; + } + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Claim [id=" + id + ", name=" + name + ", friendlyName=" + friendlyName + ", claimType=" + claimType + ", value=" + value + ", claimTokenFormat=" + claimTokenFormat + ", issuer=" + issuer + "]"; + } + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((claimTokenFormat == null) ? 0 : claimTokenFormat.hashCode()); + result = prime * result + ((claimType == null) ? 0 : claimType.hashCode()); + result = prime * result + ((friendlyName == null) ? 0 : friendlyName.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((issuer == null) ? 0 : issuer.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Claim other = (Claim) obj; + if (claimTokenFormat == null) { + if (other.claimTokenFormat != null) { + return false; + } + } else if (!claimTokenFormat.equals(other.claimTokenFormat)) { + return false; + } + if (claimType == null) { + if (other.claimType != null) { + return false; + } + } else if (!claimType.equals(other.claimType)) { + return false; + } + if (friendlyName == null) { + if (other.friendlyName != null) { + return false; + } + } else if (!friendlyName.equals(other.friendlyName)) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (issuer == null) { + if (other.issuer != null) { + return false; + } + } else if (!issuer.equals(other.issuer)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/ClaimProcessingResult.java b/openid-connect-common/src/main/java/org/mitre/uma/model/ClaimProcessingResult.java new file mode 100644 index 0000000000..0c00653307 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/ClaimProcessingResult.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model; + +import java.util.Collection; + +/** + * Data shuttle to return results of the claims processing service. + * + * @author jricher + * + */ +public class ClaimProcessingResult { + + private boolean satisfied; + private Collection unmatched; + private Policy matched; + + /** + * Create an unmatched result. isSatisfied is false. + * @param unmatched + */ + public ClaimProcessingResult(Collection unmatched) { + this.satisfied = false; + this.unmatched = unmatched; + this.matched = null; + } + + /** + * Create a matched result. isSatisfied is true. + * @param matched + */ + public ClaimProcessingResult(Policy matched) { + this.satisfied = true; + this.matched = matched; + this.unmatched = null; + } + + /** + * @return the satisfied + */ + public boolean isSatisfied() { + return satisfied; + } + + /** + * @param satisfied the satisfied to set + */ + public void setSatisfied(boolean satisfied) { + this.satisfied = satisfied; + } + + /** + * @return the unmatched + */ + public Collection getUnmatched() { + return unmatched; + } + + /** + * @param unmatched the unmatched to set + */ + public void setUnmatched(Collection unmatched) { + this.unmatched = unmatched; + } + + /** + * @return the matched + */ + public Policy getMatched() { + return matched; + } + + /** + * @param matched the matched to set + */ + public void setMatched(Policy matched) { + this.matched = matched; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/Permission.java b/openid-connect-common/src/main/java/org/mitre/uma/model/Permission.java new file mode 100644 index 0000000000..42fd2d7f7f --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/Permission.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model; + +import java.util.Set; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author jricher + */ +@Entity +@Table(name = "permission") +public class Permission { + + private Long id; + private ResourceSet resourceSet; + private Set scopes; + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the resourceSet + */ + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "resource_set_id") + public ResourceSet getResourceSet() { + return resourceSet; + } + + /** + * @param resourceSet the resourceSet to set + */ + public void setResourceSet(ResourceSet resourceSet) { + this.resourceSet = resourceSet; + } + + /** + * @return the scopes + */ + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "scope") + @CollectionTable( + name = "permission_scope", + joinColumns = @JoinColumn(name = "owner_id") + ) + public Set getScopes() { + return scopes; + } + + /** + * @param scopes the scopes to set + */ + public void setScopes(Set scopes) { + this.scopes = scopes; + } +} \ No newline at end of file diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/PermissionTicket.java b/openid-connect-common/src/main/java/org/mitre/uma/model/PermissionTicket.java new file mode 100644 index 0000000000..8b89ef5fda --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/PermissionTicket.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model; + +import java.util.Collection; +import java.util.Date; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * + * An UMA permission, used in the protection API. + * + * @author jricher + * + */ +@Entity +@Table(name = "permission_ticket") +@NamedQueries({ + @NamedQuery(name = PermissionTicket.QUERY_TICKET, query = "select p from PermissionTicket p where p.ticket = :" + PermissionTicket.PARAM_TICKET), + @NamedQuery(name = PermissionTicket.QUERY_ALL, query = "select p from PermissionTicket p"), + @NamedQuery(name = PermissionTicket.QUERY_BY_RESOURCE_SET, query = "select p from PermissionTicket p where p.permission.resourceSet.id = :" + PermissionTicket.PARAM_RESOURCE_SET_ID) +}) +public class PermissionTicket { + + public static final String QUERY_TICKET = "PermissionTicket.queryByTicket"; + public static final String QUERY_ALL = "PermissionTicket.queryAll"; + public static final String QUERY_BY_RESOURCE_SET = "PermissionTicket.queryByResourceSet"; + + public static final String PARAM_TICKET = "ticket"; + public static final String PARAM_RESOURCE_SET_ID = "rsid"; + + private Long id; + private Permission permission; + private String ticket; + private Date expiration; + private Collection claimsSupplied; + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the permission + */ + @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) + @JoinColumn(name = "permission_id") + public Permission getPermission() { + return permission; + } + + /** + * @param permission the permission to set + */ + public void setPermission(Permission permission) { + this.permission = permission; + } + + /** + * @return the ticket + */ + @Basic + @Column(name = "ticket") + public String getTicket() { + return ticket; + } + + /** + * @param ticket the ticket to set + */ + public void setTicket(String ticket) { + this.ticket = ticket; + } + + /** + * @return the expiration + */ + @Basic + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "expiration") + public Date getExpiration() { + return expiration; + } + + /** + * @param expiration the expiration to set + */ + public void setExpiration(Date expiration) { + this.expiration = expiration; + } + + /** + * @return the claimsSupplied + */ + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable( + name = "claim_to_permission_ticket", + joinColumns = @JoinColumn(name = "permission_ticket_id"), + inverseJoinColumns = @JoinColumn(name = "claim_id") + ) + public Collection getClaimsSupplied() { + return claimsSupplied; + } + + /** + * @param claimsSupplied the claimsSupplied to set + */ + public void setClaimsSupplied(Collection claimsSupplied) { + this.claimsSupplied = claimsSupplied; + } + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/Policy.java b/openid-connect-common/src/main/java/org/mitre/uma/model/Policy.java new file mode 100644 index 0000000000..32098e2fb1 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/Policy.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model; + +import java.util.Collection; +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +/** + * A set of claims required to fulfill a given permission. + * + * @author jricher + * + */ +@Entity +@Table(name = "policy") +public class Policy { + + private Long id; + private String name; + private Collection claimsRequired; + private Set scopes; + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the name + */ + @Basic + @Column(name = "name") + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the claimsRequired + */ + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinTable( + name = "claim_to_policy", + joinColumns = @JoinColumn(name = "policy_id"), + inverseJoinColumns = @JoinColumn(name = "claim_id") + ) + public Collection getClaimsRequired() { + return claimsRequired; + } + + /** + * @param claimsRequired the claimsRequired to set + */ + public void setClaimsRequired(Collection claimsRequired) { + this.claimsRequired = claimsRequired; + } + + /** + * @return the scopes + */ + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "scope") + @CollectionTable( + name = "policy_scope", + joinColumns = @JoinColumn(name = "owner_id") + ) + public Set getScopes() { + return scopes; + } + + /** + * @param scopes the scopes to set + */ + public void setScopes(Set scopes) { + this.scopes = scopes; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "Policy [id=" + id + ", name=" + name + ", claimsRequired=" + claimsRequired + ", scopes=" + scopes + "]"; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((claimsRequired == null) ? 0 : claimsRequired.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((scopes == null) ? 0 : scopes.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Policy other = (Policy) obj; + if (claimsRequired == null) { + if (other.claimsRequired != null) { + return false; + } + } else if (!claimsRequired.equals(other.claimsRequired)) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (scopes == null) { + if (other.scopes != null) { + return false; + } + } else if (!scopes.equals(other.scopes)) { + return false; + } + return true; + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/ResourceSet.java b/openid-connect-common/src/main/java/org/mitre/uma/model/ResourceSet.java new file mode 100644 index 0000000000..6303d377f2 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/ResourceSet.java @@ -0,0 +1,329 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.uma.model; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.persistence.Basic; +import javax.persistence.CascadeType; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name = "resource_set") +@NamedQueries ({ + @NamedQuery(name = ResourceSet.QUERY_BY_OWNER, query = "select r from ResourceSet r where r.owner = :" + ResourceSet.PARAM_OWNER), + @NamedQuery(name = ResourceSet.QUERY_BY_OWNER_AND_CLIENT, query = "select r from ResourceSet r where r.owner = :" + ResourceSet.PARAM_OWNER + " and r.clientId = :" + ResourceSet.PARAM_CLIENTID), + @NamedQuery(name = ResourceSet.QUERY_BY_CLIENT, query = "select r from ResourceSet r where r.clientId = :" + ResourceSet.PARAM_CLIENTID), + @NamedQuery(name = ResourceSet.QUERY_ALL, query = "select r from ResourceSet r") +}) +public class ResourceSet { + + public static final String QUERY_BY_OWNER = "ResourceSet.queryByOwner"; + public static final String QUERY_BY_OWNER_AND_CLIENT = "ResourceSet.queryByOwnerAndClient"; + public static final String QUERY_BY_CLIENT = "ResourceSet.queryByClient"; + public static final String QUERY_ALL = "ResourceSet.queryAll"; + + public static final String PARAM_OWNER = "owner"; + public static final String PARAM_CLIENTID = "clientId"; + + private Long id; + private String name; + private String uri; + private String type; + private Set scopes = new HashSet<>(); + private String iconUri; + + private String owner; // username of the person responsible for the registration (either directly or via OAuth token) + private String clientId; // client id of the protected resource that registered this resource set via OAuth token + + private Collection policies = new HashSet<>(); + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the name + */ + @Basic + @Column(name = "name") + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the uri + */ + @Basic + @Column(name = "uri") + public String getUri() { + return uri; + } + + /** + * @param uri the uri to set + */ + public void setUri(String uri) { + this.uri = uri; + } + + /** + * @return the type + */ + @Basic + @Column(name = "rs_type") + public String getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(String type) { + this.type = type; + } + + /** + * @return the scopes + */ + @ElementCollection(fetch = FetchType.EAGER) + @Column(name = "scope") + @CollectionTable( + name = "resource_set_scope", + joinColumns = @JoinColumn(name = "owner_id") + ) + public Set getScopes() { + return scopes; + } + + /** + * @param scopes the scopes to set + */ + public void setScopes(Set scopes) { + this.scopes = scopes; + } + + /** + * @return the iconUri + */ + @Basic + @Column(name = "icon_uri") + public String getIconUri() { + return iconUri; + } + + /** + * @param iconUri the iconUri to set + */ + public void setIconUri(String iconUri) { + this.iconUri = iconUri; + } + + /** + * @return the owner + */ + @Basic + @Column(name = "owner") + public String getOwner() { + return owner; + } + + /** + * @param owner the owner to set + */ + public void setOwner(String owner) { + this.owner = owner; + } + + /** + * @return the clientId + */ + @Basic + @Column(name = "client_id") + public String getClientId() { + return clientId; + } + + /** + * @param clientId the clientId to set + */ + public void setClientId(String clientId) { + this.clientId = clientId; + } + + /** + * @return the claimsRequired + */ + @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "resource_set_id") + public Collection getPolicies() { + return policies; + } + + /** + * @param policies the claimsRequired to set + */ + public void setPolicies(Collection policies) { + this.policies = policies; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "ResourceSet [id=" + id + ", name=" + name + ", uri=" + uri + ", type=" + type + ", scopes=" + scopes + ", iconUri=" + iconUri + ", owner=" + owner + ", clientId=" + clientId + ", policies=" + policies + "]"; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((clientId == null) ? 0 : clientId.hashCode()); + result = prime * result + ((iconUri == null) ? 0 : iconUri.hashCode()); + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((owner == null) ? 0 : owner.hashCode()); + result = prime * result + ((policies == null) ? 0 : policies.hashCode()); + result = prime * result + ((scopes == null) ? 0 : scopes.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((uri == null) ? 0 : uri.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ResourceSet other = (ResourceSet) obj; + if (clientId == null) { + if (other.clientId != null) { + return false; + } + } else if (!clientId.equals(other.clientId)) { + return false; + } + if (iconUri == null) { + if (other.iconUri != null) { + return false; + } + } else if (!iconUri.equals(other.iconUri)) { + return false; + } + if (id == null) { + if (other.id != null) { + return false; + } + } else if (!id.equals(other.id)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (owner == null) { + if (other.owner != null) { + return false; + } + } else if (!owner.equals(other.owner)) { + return false; + } + if (policies == null) { + if (other.policies != null) { + return false; + } + } else if (!policies.equals(other.policies)) { + return false; + } + if (scopes == null) { + if (other.scopes != null) { + return false; + } + } else if (!scopes.equals(other.scopes)) { + return false; + } + if (type == null) { + if (other.type != null) { + return false; + } + } else if (!type.equals(other.type)) { + return false; + } + if (uri == null) { + if (other.uri != null) { + return false; + } + } else if (!uri.equals(other.uri)) { + return false; + } + return true; + } + + + + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/SavedRegisteredClient.java b/openid-connect-common/src/main/java/org/mitre/uma/model/SavedRegisteredClient.java new file mode 100644 index 0000000000..4b0ed95551 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/SavedRegisteredClient.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.uma.model.convert.RegisteredClientStringConverter; + +/** + * @author jricher + * + */ +@Entity +@Table(name = "saved_registered_client") +public class SavedRegisteredClient { + + private Long id; + private String issuer; + private RegisteredClient registeredClient; + + /** + * @return the id + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + public Long getId() { + return id; + } + + /** + * + * @param id the id to set + */ + public void setId(Long id) { + this.id = id; + } + + /** + * @return the issuer + */ + @Basic + @Column(name = "issuer") + public String getIssuer() { + return issuer; + } + + /** + * @param issuer the issuer to set + */ + public void setIssuer(String issuer) { + this.issuer = issuer; + } + + /** + * @return the registeredClient + */ + @Basic + @Column(name = "registered_client") + @Convert(converter = RegisteredClientStringConverter.class) + public RegisteredClient getRegisteredClient() { + return registeredClient; + } + + /** + * @param registeredClient the registeredClient to set + */ + public void setRegisteredClient(RegisteredClient registeredClient) { + this.registeredClient = registeredClient; + } + + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/model/convert/RegisteredClientStringConverter.java b/openid-connect-common/src/main/java/org/mitre/uma/model/convert/RegisteredClientStringConverter.java new file mode 100644 index 0000000000..6a68f9da39 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/model/convert/RegisteredClientStringConverter.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.model.convert; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; + +import com.google.common.base.Strings; + +/** + * @author jricher + * + */ +@Converter +public class RegisteredClientStringConverter implements AttributeConverter{ + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToDatabaseColumn(java.lang.Object) + */ + @Override + public String convertToDatabaseColumn(RegisteredClient attribute) { + if (attribute == null || attribute.getSource() == null) { + return null; + } else { + return attribute.getSource().toString(); + } + + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public RegisteredClient convertToEntityAttribute(String dbData) { + if (Strings.isNullOrEmpty(dbData)) { + return null; + } else { + return ClientDetailsEntityJsonProcessor.parseRegistered(dbData); + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/repository/PermissionRepository.java b/openid-connect-common/src/main/java/org/mitre/uma/repository/PermissionRepository.java new file mode 100644 index 0000000000..825453dcb6 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/repository/PermissionRepository.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.repository; + +import java.util.Collection; + +import org.mitre.uma.model.Permission; +import org.mitre.uma.model.PermissionTicket; +import org.mitre.uma.model.ResourceSet; + +/** + * @author jricher + * + */ +public interface PermissionRepository { + + /** + * + * Save a permission ticket. + * + * @param p + * @return + */ + public PermissionTicket save(PermissionTicket p); + + /** + * Get the permission indicated by its ticket value. + * + * @param ticket + * @return + */ + public PermissionTicket getByTicket(String ticket); + + /** + * Get all the tickets in the system (used by the import/export API) + * + * @return + */ + public Collection getAll(); + + /** + * Save a permission object with no associated ticket (used by the import/export API) + * + * @param p + * @return + */ + public Permission saveRawPermission(Permission p); + + /** + * Get a permission object by its ID (used by the import/export API) + * + * @param permissionId + * @return + */ + public Permission getById(Long permissionId); + + /** + * Get all permission tickets issued against a resource set (called when RS is deleted) + * + * @param rs + * @return + */ + public Collection getPermissionTicketsForResourceSet(ResourceSet rs); + + /** + * Remove the specified ticket. + * + * @param ticket + */ + public void remove(PermissionTicket ticket); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/repository/ResourceSetRepository.java b/openid-connect-common/src/main/java/org/mitre/uma/repository/ResourceSetRepository.java new file mode 100644 index 0000000000..7c5a1cb73b --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/repository/ResourceSetRepository.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.repository; + +import java.util.Collection; + +import org.mitre.uma.model.ResourceSet; + +/** + * @author jricher + * + */ +public interface ResourceSetRepository { + + public ResourceSet save(ResourceSet rs); + + public ResourceSet getById(Long id); + + public void remove(ResourceSet rs); + + public Collection getAllForOwner(String owner); + + public Collection getAllForOwnerAndClient(String owner, String clientId); + + public Collection getAll(); + + public Collection getAllForClient(String clientId); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/service/ClaimsProcessingService.java b/openid-connect-common/src/main/java/org/mitre/uma/service/ClaimsProcessingService.java new file mode 100644 index 0000000000..da88c4a7c4 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/service/ClaimsProcessingService.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.service; + +import org.mitre.uma.model.ClaimProcessingResult; +import org.mitre.uma.model.PermissionTicket; +import org.mitre.uma.model.ResourceSet; + +/** + * + * Processes claims presented during an UMA transaction. + * + * @author jricher + * + */ +public interface ClaimsProcessingService { + + /** + * + * Determine whether or not the claims that have been supplied are + * sufficient to fulfill the requirements given by the claims that + * are required. + * + * @param rs the required claims to check against + * @param ticket the supplied claims to test + * @return the result of the claims processing action + */ + public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket ticket); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/service/PermissionService.java b/openid-connect-common/src/main/java/org/mitre/uma/service/PermissionService.java new file mode 100644 index 0000000000..ab7ea2e3f6 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/service/PermissionService.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.service; + +import java.util.Set; + +import org.mitre.uma.model.PermissionTicket; +import org.mitre.uma.model.ResourceSet; +import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException; + + +/** + * @author jricher + * + */ +public interface PermissionService { + + /** + * @param resourceSet the resource set to create the permission on + * @param scopes the set of scopes that this permission is for + * @return the created (and stored) permission object, with ticket + * @throws InsufficientScopeException if the scopes in scopes don't match those in resourceSet.getScopes + */ + public PermissionTicket createTicket(ResourceSet resourceSet, Set scopes); + + /** + * + * Read the permission associated with the given ticket. + * + * @param the ticket value to search on + * @return the permission object, or null if none is found + */ + public PermissionTicket getByTicket(String ticket); + + /** + * Save the updated permission ticket to the database. Does not create a new ticket. + * + * @param ticket + * @return + */ + public PermissionTicket updateTicket(PermissionTicket ticket); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/service/ResourceSetService.java b/openid-connect-common/src/main/java/org/mitre/uma/service/ResourceSetService.java new file mode 100644 index 0000000000..8da2ce017c --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/service/ResourceSetService.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.uma.service; + +import java.util.Collection; + +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.uma.model.ResourceSet; + +/** + * + * Manage registered resource sets at this authorization server. + * + * @author jricher + * + */ +public interface ResourceSetService { + + public ResourceSet saveNew(ResourceSet rs); + + public ResourceSet getById(Long id); + + public ResourceSet update(ResourceSet oldRs, ResourceSet newRs); + + public void remove(ResourceSet rs); + + public Collection getAllForOwner(String owner); + + public Collection getAllForOwnerAndClient(String owner, String authClientId); + + public Collection getAllForClient(ClientDetailsEntity client); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/service/SavedRegisteredClientService.java b/openid-connect-common/src/main/java/org/mitre/uma/service/SavedRegisteredClientService.java new file mode 100644 index 0000000000..4b8745c961 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/service/SavedRegisteredClientService.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.service; + +import java.util.Collection; + +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.uma.model.SavedRegisteredClient; + +/** + * @author jricher + * + */ +public interface SavedRegisteredClientService { + + /** + * Get a list of all the registered clients that we know about. + * + * @return + */ + Collection getAll(); + + /** + * @param issuer + * @param client + */ + void save(String issuer, RegisteredClient client); + + +} diff --git a/openid-connect-common/src/main/java/org/mitre/uma/service/UmaTokenService.java b/openid-connect-common/src/main/java/org/mitre/uma/service/UmaTokenService.java new file mode 100644 index 0000000000..8ee6c86b15 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/uma/service/UmaTokenService.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ + +package org.mitre.uma.service; + +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.uma.model.PermissionTicket; +import org.mitre.uma.model.Policy; +import org.springframework.security.oauth2.provider.OAuth2Authentication; + +/** + * Service to create special tokens for UMA. + * + * @author jricher + * + */ +public interface UmaTokenService { + + /** + * Create the RPT from the given authentication and ticket. + * + */ + public OAuth2AccessTokenEntity createRequestingPartyToken(OAuth2Authentication o2auth, PermissionTicket ticket, Policy policy); + +} diff --git a/openid-connect-common/src/main/java/org/mitre/util/JsonUtils.java b/openid-connect-common/src/main/java/org/mitre/util/JsonUtils.java new file mode 100644 index 0000000000..b17f150be6 --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/util/JsonUtils.java @@ -0,0 +1,347 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +/** + * + */ +package org.mitre.util; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.mitre.oauth2.model.PKCEAlgorithm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWSAlgorithm; + +/** + * A collection of null-safe converters from common classes and JSON elements, using GSON. + * + * @author jricher + * + */ +@SuppressWarnings(value = {"rawtypes", "unchecked"}) +public class JsonUtils { + + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(JsonUtils.class); + + private static Gson gson = new Gson(); + + /** + * Translate a set of strings to a JSON array, empty array returned as null + * @param value + * @return + */ + public static JsonElement getAsArray(Set value) { + return getAsArray(value, false); + } + + + /** + * Translate a set of strings to a JSON array, optionally preserving the empty array. Otherwise (default) empty array is returned as null. + * @param value + * @param preserveEmpty + * @return + */ + public static JsonElement getAsArray(Set value, boolean preserveEmpty) { + if (!preserveEmpty && value != null && value.isEmpty()) { + // if we're not preserving empty arrays and the value is empty, return null + return JsonNull.INSTANCE; + } else { + return gson.toJsonTree(value, new TypeToken>(){}.getType()); + } + } + + /** + * Gets the value of the given member (expressed as integer seconds since epoch) as a Date + */ + public static Date getAsDate(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return new Date(e.getAsInt() * 1000L); + } else { + return null; + } + } else { + return null; + } + } + + /** + * Gets the value of the given member as a JWE Algorithm, null if it doesn't exist + */ + public static JWEAlgorithm getAsJweAlgorithm(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return JWEAlgorithm.parse(s); + } else { + return null; + } + } + + /** + * Gets the value of the given member as a JWE Encryption Method, null if it doesn't exist + */ + public static EncryptionMethod getAsJweEncryptionMethod(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return EncryptionMethod.parse(s); + } else { + return null; + } + } + + /** + * Gets the value of the given member as a JWS Algorithm, null if it doesn't exist + */ + public static JWSAlgorithm getAsJwsAlgorithm(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return JWSAlgorithm.parse(s); + } else { + return null; + } + } + + /** + * Gets the value of the given member as a PKCE Algorithm, null if it doesn't exist + * @param o + * @param member + * @return + */ + public static PKCEAlgorithm getAsPkceAlgorithm(JsonObject o, String member) { + String s = getAsString(o, member); + if (s != null) { + return PKCEAlgorithm.parse(s); + } else { + return null; + } + } + + /** + * Gets the value of the given member as a string, null if it doesn't exist + */ + public static String getAsString(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return e.getAsString(); + } else { + return null; + } + } else { + return null; + } + } + + /** + * Gets the value of the given member as a boolean, null if it doesn't exist + */ + public static Boolean getAsBoolean(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return e.getAsBoolean(); + } else { + return null; + } + } else { + return null; + } + } + + /** + * Gets the value of the given member as a Long, null if it doesn't exist + */ + public static Long getAsLong(JsonObject o, String member) { + if (o.has(member)) { + JsonElement e = o.get(member); + if (e != null && e.isJsonPrimitive()) { + return e.getAsLong(); + } else { + return null; + } + } else { + return null; + } + } + + /** + * Gets the value of the given given member as a set of strings, null if it doesn't exist + */ + public static Set getAsStringSet(JsonObject o, String member) throws JsonSyntaxException { + if (o.has(member)) { + if (o.get(member).isJsonArray()) { + return gson.fromJson(o.get(member), new TypeToken>(){}.getType()); + } else { + return Sets.newHashSet(o.get(member).getAsString()); + } + } else { + return null; + } + } + + /** + * Gets the value of the given given member as a set of strings, null if it doesn't exist + */ + public static List getAsStringList(JsonObject o, String member) throws JsonSyntaxException { + if (o.has(member)) { + if (o.get(member).isJsonArray()) { + return gson.fromJson(o.get(member), new TypeToken>(){}.getType()); + } else { + return Lists.newArrayList(o.get(member).getAsString()); + } + } else { + return null; + } + } + + /** + * Gets the value of the given member as a list of JWS Algorithms, null if it doesn't exist + */ + public static List getAsJwsAlgorithmList(JsonObject o, String member) { + List strings = getAsStringList(o, member); + if (strings != null) { + List algs = new ArrayList<>(); + for (String alg : strings) { + algs.add(JWSAlgorithm.parse(alg)); + } + return algs; + } else { + return null; + } + } + + /** + * Gets the value of the given member as a list of JWS Algorithms, null if it doesn't exist + */ + public static List getAsJweAlgorithmList(JsonObject o, String member) { + List strings = getAsStringList(o, member); + if (strings != null) { + List algs = new ArrayList<>(); + for (String alg : strings) { + algs.add(JWEAlgorithm.parse(alg)); + } + return algs; + } else { + return null; + } + } + + /** + * Gets the value of the given member as a list of JWS Algorithms, null if it doesn't exist + */ + public static List getAsEncryptionMethodList(JsonObject o, String member) { + List strings = getAsStringList(o, member); + if (strings != null) { + List algs = new ArrayList<>(); + for (String alg : strings) { + algs.add(EncryptionMethod.parse(alg)); + } + return algs; + } else { + return null; + } + } + + public static Map readMap(JsonReader reader) throws IOException { + Map map = new HashMap<>(); + reader.beginObject(); + while(reader.hasNext()) { + String name = reader.nextName(); + Object value = null; + switch(reader.peek()) { + case STRING: + value = reader.nextString(); + break; + case BOOLEAN: + value = reader.nextBoolean(); + break; + case NUMBER: + value = reader.nextLong(); + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + map.put(name, value); + } + reader.endObject(); + return map; + } + + public static Set readSet(JsonReader reader) throws IOException { + Set arraySet = null; + reader.beginArray(); + switch (reader.peek()) { + case STRING: + arraySet = new HashSet<>(); + while (reader.hasNext()) { + arraySet.add(reader.nextString()); + } + break; + case NUMBER: + arraySet = new HashSet<>(); + while (reader.hasNext()) { + arraySet.add(reader.nextLong()); + } + break; + default: + arraySet = new HashSet(); + break; + } + reader.endArray(); + return arraySet; + } + + public static void writeNullSafeArray(JsonWriter writer, Set items) throws IOException { + if (items != null) { + writer.beginArray(); + for (String s : items) { + writer.value(s); + } + writer.endArray(); + } else { + writer.nullValue(); + } + } + +} diff --git a/openid-connect-common/src/main/java/org/mitre/util/jpa/JpaUtil.java b/openid-connect-common/src/main/java/org/mitre/util/jpa/JpaUtil.java index f60e8b65bb..f15e4c371c 100644 --- a/openid-connect-common/src/main/java/org/mitre/util/jpa/JpaUtil.java +++ b/openid-connect-common/src/main/java/org/mitre/util/jpa/JpaUtil.java @@ -1,24 +1,28 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.util.jpa; import java.util.List; import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import org.mitre.data.PageCriteria; /** * @author mfranklin @@ -28,24 +32,37 @@ public class JpaUtil { public static T getSingleResult(List list) { switch(list.size()) { - case 0: - return null; - case 1: - return list.get(0); - default: - throw new IllegalStateException("Expected single result, got " + list.size()); + case 0: + return null; + case 1: + return list.get(0); + default: + throw new IllegalStateException("Expected single result, got " + list.size()); } } + + /** + * Get a page of results from the specified TypedQuery + * by using the given PageCriteria to limit the query + * results. The PageCriteria will override any size or + * offset already specified on the query. + * + * @param the type parameter + * @param query the query + * @param pageCriteria the page criteria + * @return the list + */ + public static List getResultPage(TypedQuery query, PageCriteria pageCriteria){ + query.setMaxResults(pageCriteria.getPageSize()); + query.setFirstResult(pageCriteria.getPageNumber()*pageCriteria.getPageSize()); + + return query.getResultList(); + } + public static T saveOrUpdate(I id, EntityManager entityManager, T entity) { - if (id == null) { - entityManager.persist(entity); - entityManager.flush(); - return entity; - } else { - T tmp = entityManager.merge(entity); - entityManager.flush(); - return tmp; - } + T tmp = entityManager.merge(entity); + entityManager.flush(); + return tmp; } } diff --git a/openid-connect-common/src/test/java/org/mitre/data/AbstractPageOperationTemplateTest.java b/openid-connect-common/src/test/java/org/mitre/data/AbstractPageOperationTemplateTest.java new file mode 100644 index 0000000000..1bf6d1e3f0 --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/data/AbstractPageOperationTemplateTest.java @@ -0,0 +1,231 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.data; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Colm Smyth + */ +public class AbstractPageOperationTemplateTest { + + @Before + public void setUp() throws Exception { + } + + @Test(timeout = 1000L) + public void execute_zeropages() { + CountingPageOperation op = new CountingPageOperation(0,Long.MAX_VALUE); + op.execute(); + + assertEquals(0L, op.counter); + } + + @Test(timeout = 1000L) + public void execute_singlepage() { + CountingPageOperation op = new CountingPageOperation(1,Long.MAX_VALUE); + op.execute(); + + assertEquals(10L, op.counter); + } + + @Test(timeout = 1000L) + public void execute_negpage() { + CountingPageOperation op = new CountingPageOperation(-1,Long.MAX_VALUE); + op.execute(); + + assertEquals(0L, op.counter); + } + + @Test(timeout = 1000L) + public void execute_npage(){ + int n = 7; + CountingPageOperation op = new CountingPageOperation(n,Long.MAX_VALUE); + op.execute(); + + assertEquals(n*10L, op.counter); + } + + @Test(timeout = 1000L) + public void execute_nullpage(){ + CountingPageOperation op = new NullPageCountingPageOperation(Integer.MAX_VALUE, Long.MAX_VALUE); + op.execute(); + + assertEquals(0L, op.getCounter()); + } + + @Test(timeout = 1000L) + public void execute_emptypage(){ + CountingPageOperation op = new EmptyPageCountingPageOperation(Integer.MAX_VALUE, Long.MAX_VALUE); + op.execute(); + + assertEquals(0L, op.getCounter()); + } + + @Test(timeout = 1000L) + public void execute_zerotime(){ + CountingPageOperation op = new CountingPageOperation(Integer.MAX_VALUE,0L); + op.execute(); + + assertEquals(0L, op.getCounter()); + assertEquals(0L, op.getTimeToLastFetch()); + } + + /* + * This is a valid test however it is vulnerable to a race condition + * as such it is being ignored. + */ + @Test(timeout = 1000L) + @Ignore + public void execute_nonzerotime(){ + Long timeMillis = 200L; + CountingPageOperation op = new CountingPageOperation(Integer.MAX_VALUE,timeMillis); + op.execute(); + + assertFalse("last fetch time " + op.getTimeToLastFetch() + "" + + " and previous fetch time " + op.getTimeToPreviousFetch() + + " exceed max time" + timeMillis, + op.getTimeToLastFetch() > timeMillis + && op.getTimeToPreviousFetch() > timeMillis); + } + + @Test(timeout = 1000L) + public void execute_negtime(){ + Long timeMillis = -100L; + CountingPageOperation op = new CountingPageOperation(Integer.MAX_VALUE,timeMillis); + op.execute(); + + assertEquals(0L, op.getCounter()); + } + + @Test(timeout = 1000L) + public void execute_swallowException(){ + CountingPageOperation op = new EvenExceptionCountingPageOperation(1, 1000L); + op.execute(); + + assertTrue(op.isSwallowExceptions()); + assertEquals(5L, op.getCounter()); + } + + @Test(expected = IllegalStateException.class) + public void execute_noSwallowException(){ + CountingPageOperation op = new EvenExceptionCountingPageOperation(1, 1000L); + op.setSwallowExceptions(false); + + try { + op.execute(); + }finally { + assertEquals(1L, op.getCounter()); + } + } + + + private static class CountingPageOperation extends AbstractPageOperationTemplate{ + + private int currentPageFetch; + private int pageSize = 10; + private long counter = 0L; + private long startTime; + private long timeToLastFetch; + private long timeToPreviousFetch; + + private CountingPageOperation(int maxPages, long maxTime) { + super(maxPages, maxTime, "CountingPageOperation"); + startTime = System.currentTimeMillis(); + } + + @Override + public Collection fetchPage() { + timeToPreviousFetch = timeToLastFetch > 0 ? timeToLastFetch : 0; + timeToLastFetch = System.currentTimeMillis() - startTime; + + List page = new ArrayList(pageSize); + for(int i = 0; i < pageSize; i++ ) { + page.add("item " + currentPageFetch * pageSize + i); + } + currentPageFetch++; + return page; + } + + @Override + protected void doOperation(String item) { + counter++; + } + + public long getCounter() { + return counter; + } + + public long getTimeToLastFetch() { + return timeToLastFetch; + } + + public long getTimeToPreviousFetch() { + return timeToPreviousFetch; + } + } + + private static class NullPageCountingPageOperation extends CountingPageOperation { + private NullPageCountingPageOperation(int maxPages, long maxTime) { + super(maxPages, maxTime); + } + + @Override + public Collection fetchPage() { + return null; + } + } + + private static class EmptyPageCountingPageOperation extends CountingPageOperation { + private EmptyPageCountingPageOperation(int maxPages, long maxTime) { + super(maxPages, maxTime); + } + + @Override + public Collection fetchPage() { + return new ArrayList<>(0); + } + } + + private static class EvenExceptionCountingPageOperation extends CountingPageOperation { + + private int callCounter; + private EvenExceptionCountingPageOperation(int maxPages, long maxTime) { + super(maxPages, maxTime); + } + + @Override + protected void doOperation(String item) { + callCounter++; + if(callCounter%2 == 0){ + throw new IllegalStateException("even number items cannot be processed"); + } + + super.doOperation(item); + + } + } +} diff --git a/openid-connect-common/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java b/openid-connect-common/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java index 4844504781..34b4943603 100644 --- a/openid-connect-common/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java +++ b/openid-connect-common/src/test/java/org/mitre/discovery/util/TestWebfingerURLNormalizer.java @@ -1,28 +1,29 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ package org.mitre.discovery.util; -import static org.junit.Assert.assertEquals; - import org.junit.Test; import org.springframework.web.util.UriComponents; import com.google.common.collect.ImmutableMap; +import static org.junit.Assert.assertEquals; + /** * @author wkim * diff --git a/openid-connect-common/src/test/java/org/mitre/jose/JOSEEmbedTest.java b/openid-connect-common/src/test/java/org/mitre/jose/JOSEEmbedTest.java deleted file mode 100644 index af7acc3bda..0000000000 --- a/openid-connect-common/src/test/java/org/mitre/jose/JOSEEmbedTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -/** - * - */ -package org.mitre.jose; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -import com.nimbusds.jose.EncryptionMethod; -import com.nimbusds.jose.JWEAlgorithm; -import com.nimbusds.jose.JWSAlgorithm; - -/** - * - * These tests make sure that the algorithm name processing - * is functional on the three embedded JOSE classes. - * - * @author jricher - * - */ -public class JOSEEmbedTest { - - @Test - public void testJWSAlgorithmEmbed() { - JWSAlgorithmEmbed a = new JWSAlgorithmEmbed(JWSAlgorithm.HS256); - - assertEquals(JWSAlgorithm.HS256, a.getAlgorithm()); - assertEquals("HS256", a.getAlgorithmName()); - } - - @Test - public void testJWSAlgorithmEmbedGetForAlgoirthmName() { - JWSAlgorithmEmbed a = JWSAlgorithmEmbed.getForAlgorithmName("RS256"); - - assertEquals(JWSAlgorithm.RS256, a.getAlgorithm()); - assertEquals("RS256", a.getAlgorithmName()); - } - - @Test - public void testJWEAlgorithmEmbed() { - JWEAlgorithmEmbed a = new JWEAlgorithmEmbed(JWEAlgorithm.A128KW); - - assertEquals(JWEAlgorithm.A128KW, a.getAlgorithm()); - assertEquals("A128KW", a.getAlgorithmName()); - } - - @Test - public void testJWEAlgorithmEmbedGetForAlgoirthmName() { - JWEAlgorithmEmbed a = JWEAlgorithmEmbed.getForAlgorithmName("RSA1_5"); - - assertEquals(JWEAlgorithm.RSA1_5, a.getAlgorithm()); - assertEquals("RSA1_5", a.getAlgorithmName()); - } - - @Test - public void testJWEEncryptionMethodEmbed() { - JWEEncryptionMethodEmbed a = new JWEEncryptionMethodEmbed(EncryptionMethod.A128CBC_HS256); - - assertEquals(EncryptionMethod.A128CBC_HS256, a.getAlgorithm()); - assertEquals("A128CBC-HS256", a.getAlgorithmName()); - } - - @Test - public void testJWEEncryptionMethodEmbedGetForAlgoirthmName() { - JWEEncryptionMethodEmbed a = JWEEncryptionMethodEmbed.getForAlgorithmName("A256GCM"); - - assertEquals(EncryptionMethod.A256GCM, a.getAlgorithm()); - assertEquals("A256GCM", a.getAlgorithmName()); - } - -} diff --git a/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java b/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java new file mode 100644 index 0000000000..870e1bb3be --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.jose; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mitre.jose.keystore.JWKSetKeyStore; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.util.Base64URL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +/** + * @author tsitkov + * + */ + +public class TestJWKSetKeyStore { + + private String RSAkid = "rsa_1"; + private JWK RSAjwk = new RSAKey( + new Base64URL("oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw"), // n + new Base64URL("AQAB"), // e + new Base64URL("kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d + KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null, null); + + private String RSAkid_rsa2 = "rsa_2"; + private JWK RSAjwk_rsa2 = new RSAKey( + new Base64URL("oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw"), // n + new Base64URL("AQAB"), // e + new Base64URL("kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d + KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_rsa2, null, null, null, null, null); + + + List keys_list = new LinkedList<>(); + private JWKSet jwkSet; + private String ks_file = "ks.txt"; + private String ks_file_badJWK = "ks_badJWK.txt"; + + @Before + public void prepare() throws IOException { + + keys_list.add(RSAjwk); + keys_list.add(RSAjwk_rsa2); + jwkSet = new JWKSet(keys_list); + jwkSet.getKeys(); + + byte jwtbyte[] = jwkSet.toString().getBytes(); + FileOutputStream out = new FileOutputStream(ks_file); + out.write(jwtbyte); + out.close(); + } + + @After + public void cleanup() throws IOException { + + File f1 = new File(ks_file); + if (f1.exists()) { + f1.delete(); + } + File f2 = new File(ks_file_badJWK); + if (f2.exists()) { + f2.delete(); + } + } + + /* Constructors with no valid Resource setup */ + @Test + public void ksConstructorTest() { + + JWKSetKeyStore ks = new JWKSetKeyStore(jwkSet); + assertEquals(ks.getJwkSet(), jwkSet); + + JWKSetKeyStore ks_empty= new JWKSetKeyStore(); + assertEquals(ks_empty.getJwkSet(), null); + + boolean thrown = false; + try { + new JWKSetKeyStore(null); + } catch (IllegalArgumentException e) { + thrown = true; + } + assertTrue(thrown); + } + + /* Misformatted JWK */ + @Test(expected=IllegalArgumentException.class) + public void ksBadJWKinput() throws IOException { + + byte jwtbyte[] = RSAjwk.toString().getBytes(); + FileOutputStream out = new FileOutputStream(ks_file_badJWK); + out.write(jwtbyte); + out.close(); + + JWKSetKeyStore ks_badJWK = new JWKSetKeyStore(); + Resource loc = new FileSystemResource(ks_file_badJWK); + assertTrue(loc.exists()); + + ks_badJWK.setLocation(loc); + assertEquals(loc.getFilename(), ks_file_badJWK); + + ks_badJWK = new JWKSetKeyStore(null); + } + + /* Empty constructor with valid Resource */ + @Test + public void ksEmptyConstructorkLoc() { + + JWKSetKeyStore ks = new JWKSetKeyStore(); + + File file = new File(ks_file); + + Resource loc = new FileSystemResource(file); + assertTrue(loc.exists()); + assertTrue(loc.isReadable()); + + ks.setLocation(loc); + + assertEquals(loc.getFilename(),ks.getLocation().getFilename()); + } + + + @Test + public void ksSetJwkSet() throws IllegalArgumentException { + + JWKSetKeyStore ks = new JWKSetKeyStore(); + boolean thrown = false; + try { + ks.setJwkSet(null); + } catch (IllegalArgumentException e) { + thrown = true; + } + assertTrue(thrown); + + ks.setJwkSet(jwkSet);; + assertEquals(ks.getJwkSet(), jwkSet); + } +} diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java b/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java new file mode 100644 index 0000000000..18134b411f --- /dev/null +++ b/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJWTEncryptionAndDecryptionService.java @@ -0,0 +1,391 @@ +/******************************************************************************* + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package org.mitre.jwt.encryption.service.impl; + +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.text.ParseException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.crypto.Cipher; + +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mitre.jose.keystore.JWKSetKeyStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.ImmutableMap; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWEHeader; +import com.nimbusds.jose.JWEObject; +import com.nimbusds.jose.jca.JCASupport; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.KeyUse; +import com.nimbusds.jose.jwk.OctetSequenceKey; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jose.util.JSONObjectUtils; +import com.nimbusds.jwt.EncryptedJWT; +import com.nimbusds.jwt.JWTClaimsSet; + +import static org.hamcrest.CoreMatchers.nullValue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + + +/** + * @author wkim + * @author tsitkov + * + */ + +public class TestDefaultJWTEncryptionAndDecryptionService { + + private static Logger logger = LoggerFactory.getLogger(TestDefaultJWTEncryptionAndDecryptionService.class); + + private String plainText = "The true sign of intelligence is not knowledge but imagination."; + + private String issuer = "www.example.net"; + private String subject = "example_user"; + private JWTClaimsSet claimsSet = null; + + @Rule + public ExpectedException exception = ExpectedException.none(); + + // Example data taken from rfc7516 appendix A + private String compactSerializedJwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ." + + "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe" + + "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb" + + "Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV" + + "mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8" + + "1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi" + + "6UklfCpIMfIjf7iGdXKHzg." + + "48V1_ALb6US04U3b." + + "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji" + + "SdiwkIr3ajwQzaBtQD_A." + + "XFBoMYUZodetZdvTiFvSkQ"; + + private String RSAkid = "rsa321"; + private JWK RSAjwk = new RSAKey( + new Base64URL("oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw"), // n + new Base64URL("AQAB"), // e + new Base64URL("kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d + KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null, null, null); + + private String RSAkid_2 = "rsa3210"; + private JWK RSAjwk_2 = new RSAKey( + new Base64URL("oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + + "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + + "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + + "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + + "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + + "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw"), // n + new Base64URL("AQAB"), // e + new Base64URL("kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + + "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + + "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + + "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + + "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + + "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d + KeyUse.ENCRYPTION, null, JWEAlgorithm.RSA1_5, RSAkid_2, null, null, null, null, null); + + private String AESkid = "aes123"; + private JWK AESjwk = new OctetSequenceKey(new Base64URL("GawgguFyGrWKav7AX4VKUg"), + KeyUse.ENCRYPTION, null, JWEAlgorithm.A128KW, + AESkid, null, null, null, null, null); + + + private Map keys = new ImmutableMap.Builder() + .put(RSAkid, RSAjwk) + .build(); + private Map keys_2 = new ImmutableMap.Builder() + .put(RSAkid, RSAjwk) + .put(RSAkid_2, RSAjwk_2) + .build(); + private Map keys_3 = new ImmutableMap.Builder() + .put(AESkid, AESjwk) + .build(); + private Map keys_4= new ImmutableMap.Builder() + .put(RSAkid, RSAjwk) + .put(RSAkid_2, RSAjwk_2) + .put(AESkid, AESjwk) + .build(); + + + private List keys_list = new LinkedList<>(); + + private DefaultJWTEncryptionAndDecryptionService service; + private DefaultJWTEncryptionAndDecryptionService service_2; + private DefaultJWTEncryptionAndDecryptionService service_3; + private DefaultJWTEncryptionAndDecryptionService service_4; + private DefaultJWTEncryptionAndDecryptionService service_ks; + + + @Before + public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException { + + service = new DefaultJWTEncryptionAndDecryptionService(keys); + service_2 = new DefaultJWTEncryptionAndDecryptionService(keys_2); + service_3 = new DefaultJWTEncryptionAndDecryptionService(keys_3); + service_4 = new DefaultJWTEncryptionAndDecryptionService(keys_4); + + claimsSet = new JWTClaimsSet.Builder() + .issuer(issuer) + .subject(subject) + .build(); + + // Key Store + + keys_list.add(RSAjwk); + keys_list.add(AESjwk); + JWKSet jwkSet = new JWKSet(keys_list); + JWKSetKeyStore keyStore = new JWKSetKeyStore(jwkSet); + + service_ks = new DefaultJWTEncryptionAndDecryptionService(keyStore); + } + + + @Test + public void decrypt_RSA() throws ParseException, NoSuchAlgorithmException { + + Assume.assumeTrue(JCASupport.isSupported(JWEAlgorithm.RSA_OAEP) // check for algorithm support + && JCASupport.isSupported(EncryptionMethod.A256GCM) + && Cipher.getMaxAllowedKeyLength("RC5") >= 256); // check for unlimited crypto strength + + service.setDefaultDecryptionKeyId(RSAkid); + service.setDefaultEncryptionKeyId(RSAkid); + + JWEObject jwt = JWEObject.parse(compactSerializedJwe); + + assertThat(jwt.getPayload(), nullValue()); // observe..nothing is there + + service.decryptJwt(jwt); + String result = jwt.getPayload().toString(); // and voila! decrypto-magic + + assertEquals(plainText, result); + } + + + @Test + public void encryptThenDecrypt_RSA() throws ParseException, NoSuchAlgorithmException { + + Assume.assumeTrue(JCASupport.isSupported(JWEAlgorithm.RSA_OAEP) // check for algorithm support + && JCASupport.isSupported(EncryptionMethod.A256GCM) + && Cipher.getMaxAllowedKeyLength("RC5") >= 256); // check for unlimited crypto strength + + service.setDefaultDecryptionKeyId(RSAkid); + service.setDefaultEncryptionKeyId(RSAkid); + + assertEquals(RSAkid,service.getDefaultEncryptionKeyId()); + assertEquals(RSAkid,service.getDefaultDecryptionKeyId()); + + JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A256GCM); + + EncryptedJWT jwt = new EncryptedJWT(header, claimsSet); + + service.encryptJwt(jwt); + String serialized = jwt.serialize(); + + EncryptedJWT encryptedJwt = EncryptedJWT.parse(serialized); + assertThat(encryptedJwt.getJWTClaimsSet(), nullValue()); + service.decryptJwt(encryptedJwt); + + JWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); + + assertEquals(claimsSet.getIssuer(), resultClaims.getIssuer()); + assertEquals(claimsSet.getSubject(), resultClaims.getSubject()); + } + + + // The same as encryptThenDecrypt_RSA() but relies on the key from the map + @Test + public void encryptThenDecrypt_nullID() throws ParseException, NoSuchAlgorithmException { + + Assume.assumeTrue(JCASupport.isSupported(JWEAlgorithm.RSA_OAEP) // check for algorithm support + && JCASupport.isSupported(EncryptionMethod.A256GCM) + && Cipher.getMaxAllowedKeyLength("RC5") >= 256); // check for unlimited crypto strength + + service.setDefaultDecryptionKeyId(null); + service.setDefaultEncryptionKeyId(null); + + assertEquals(RSAkid,service.getDefaultEncryptionKeyId()); + assertEquals(RSAkid,service.getDefaultDecryptionKeyId()); + + JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A256GCM); + + EncryptedJWT jwt = new EncryptedJWT(header, claimsSet); + + service.encryptJwt(jwt); + String serialized = jwt.serialize(); + + EncryptedJWT encryptedJwt = EncryptedJWT.parse(serialized); + assertThat(encryptedJwt.getJWTClaimsSet(), nullValue()); + service.decryptJwt(encryptedJwt); + + JWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); + + assertEquals(claimsSet.getIssuer(), resultClaims.getIssuer()); + assertEquals(claimsSet.getSubject(), resultClaims.getSubject()); + } + + + @Test + public void encrypt_nullID_oneKey() throws NoSuchAlgorithmException { + + Assume.assumeTrue(JCASupport.isSupported(JWEAlgorithm.RSA_OAEP) // check for algorithm support + && JCASupport.isSupported(EncryptionMethod.A256GCM) + && Cipher.getMaxAllowedKeyLength("RC5") >= 256); // check for unlimited crypto strength + + exception.expect(IllegalStateException.class); + + service_2.setDefaultEncryptionKeyId(null); + assertEquals(null, service_2.getDefaultEncryptionKeyId()); + + JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A256GCM); + + EncryptedJWT jwt = new EncryptedJWT(header, claimsSet); + + service_2.encryptJwt(jwt); + assertEquals(null, service_2.getDefaultEncryptionKeyId()); + } + + + @Test + public void decrypt_nullID() throws ParseException, NoSuchAlgorithmException { + + Assume.assumeTrue(JCASupport.isSupported(JWEAlgorithm.RSA_OAEP) // check for algorithm support + && JCASupport.isSupported(EncryptionMethod.A256GCM) + && Cipher.getMaxAllowedKeyLength("RC5") >= 256); // check for unlimited crypto strength + + + exception.expect(IllegalStateException.class); + + service_2.setDefaultEncryptionKeyId(RSAkid); + service_2.setDefaultDecryptionKeyId(null); + + assertEquals(RSAkid, service_2.getDefaultEncryptionKeyId()); + assertEquals(null, service_2.getDefaultDecryptionKeyId()); + + JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A256GCM); + + EncryptedJWT jwt = new EncryptedJWT(header, claimsSet); + service_2.encryptJwt(jwt); + String serialized = jwt.serialize(); + + EncryptedJWT encryptedJwt = EncryptedJWT.parse(serialized); + assertThat(encryptedJwt.getJWTClaimsSet(), nullValue()); + + assertEquals(null, service_2.getDefaultDecryptionKeyId()); + service_2.decryptJwt(encryptedJwt); + } + + + @Test + public void setThenGetDefAlg() throws ParseException { + + service.setDefaultAlgorithm(JWEAlgorithm.A128KW); + assertEquals(JWEAlgorithm.A128KW, service.getDefaultAlgorithm()); + + service.setDefaultAlgorithm(JWEAlgorithm.RSA_OAEP); + assertEquals(JWEAlgorithm.RSA_OAEP, service.getDefaultAlgorithm()); + } + + + @Test + public void getAllPubKeys() throws ParseException { + + Map keys2check = service_2.getAllPublicKeys(); + assertEquals( + JSONObjectUtils.getString(RSAjwk.toPublicJWK().toJSONObject(), "e"), + JSONObjectUtils.getString(keys2check.get(RSAkid).toJSONObject(), "e") + ); + assertEquals( + JSONObjectUtils.getString(RSAjwk_2.toPublicJWK().toJSONObject(), "e"), + JSONObjectUtils.getString(keys2check.get(RSAkid_2).toJSONObject(), "e") + ); + + assertTrue(service_3.getAllPublicKeys().isEmpty()); + } + + + @Test + public void getAllCryptoAlgsSupported() throws ParseException { + + assertTrue(service_4.getAllEncryptionAlgsSupported().contains(JWEAlgorithm.RSA_OAEP)); + assertTrue(service_4.getAllEncryptionAlgsSupported().contains(JWEAlgorithm.RSA1_5)); + assertTrue(service_4.getAllEncryptionAlgsSupported().contains(JWEAlgorithm.DIR)); + assertTrue(service_4.getAllEncryptionEncsSupported().contains(EncryptionMethod.A128CBC_HS256)); + assertTrue(service_4.getAllEncryptionEncsSupported().contains(EncryptionMethod.A128GCM)); + assertTrue(service_4.getAllEncryptionEncsSupported().contains(EncryptionMethod.A192CBC_HS384)); + assertTrue(service_4.getAllEncryptionEncsSupported().contains(EncryptionMethod.A192GCM)); + assertTrue(service_4.getAllEncryptionEncsSupported().contains(EncryptionMethod.A256GCM)); + assertTrue(service_4.getAllEncryptionEncsSupported().contains(EncryptionMethod.A256CBC_HS512)); + + assertTrue(service_ks.getAllEncryptionAlgsSupported().contains(JWEAlgorithm.RSA_OAEP)); + assertTrue(service_ks.getAllEncryptionAlgsSupported().contains(JWEAlgorithm.RSA1_5)); + assertTrue(service_ks.getAllEncryptionAlgsSupported().contains(JWEAlgorithm.DIR)); + assertTrue(service_ks.getAllEncryptionEncsSupported().contains(EncryptionMethod.A128CBC_HS256)); + assertTrue(service_ks.getAllEncryptionEncsSupported().contains(EncryptionMethod.A128GCM)); + assertTrue(service_ks.getAllEncryptionEncsSupported().contains(EncryptionMethod.A192CBC_HS384)); + assertTrue(service_ks.getAllEncryptionEncsSupported().contains(EncryptionMethod.A192GCM)); + assertTrue(service_ks.getAllEncryptionEncsSupported().contains(EncryptionMethod.A256GCM)); + assertTrue(service_ks.getAllEncryptionEncsSupported().contains(EncryptionMethod.A256CBC_HS512)); + } + + + @Test + public void getDefaultCryptoKeyId() throws ParseException { + + // Test set/getDefaultEn/DecryptionKeyId + + assertEquals(null, service_4.getDefaultEncryptionKeyId()); + assertEquals(null, service_4.getDefaultDecryptionKeyId()); + service_4.setDefaultEncryptionKeyId(RSAkid); + service_4.setDefaultDecryptionKeyId(AESkid); + assertEquals(RSAkid, service_4.getDefaultEncryptionKeyId()); + assertEquals(AESkid, service_4.getDefaultDecryptionKeyId()); + + assertEquals(null, service_ks.getDefaultEncryptionKeyId()); + assertEquals(null, service_ks.getDefaultDecryptionKeyId()); + service_ks.setDefaultEncryptionKeyId(RSAkid); + service_ks.setDefaultDecryptionKeyId(AESkid); + assertEquals( RSAkid, service_ks.getDefaultEncryptionKeyId()) ; + assertEquals(AESkid, service_ks.getDefaultDecryptionKeyId()); + } +} diff --git a/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJwtEncryptionAndDecryptionService.java b/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJwtEncryptionAndDecryptionService.java deleted file mode 100644 index 9c87321f9b..0000000000 --- a/openid-connect-common/src/test/java/org/mitre/jwt/encryption/service/impl/TestDefaultJwtEncryptionAndDecryptionService.java +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - ******************************************************************************/ -package org.mitre.jwt.encryption.service.impl; - -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.text.ParseException; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; - -import com.google.common.collect.ImmutableMap; -import com.nimbusds.jose.EncryptionMethod; -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWEAlgorithm; -import com.nimbusds.jose.JWEHeader; -import com.nimbusds.jose.JWEObject; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.Use; -import com.nimbusds.jose.util.Base64URL; -import com.nimbusds.jwt.EncryptedJWT; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; - -/** - * @author wkim - * - */ -public class TestDefaultJwtEncryptionAndDecryptionService { - - private String plainText = "The true sign of intelligence is not knowledge but imagination."; - - private String issuer = "www.example.net"; - private String subject = "example_user"; - private JWTClaimsSet claimsSet = new JWTClaimsSet(); - - // Example data taken from Mike Jones's draft-ietf-jose-json-web-encryption-14 appendix examples - private String compactSerializedJwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ." + - "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe" + - "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb" + - "Sv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaV" + - "mqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je8" + - "1860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi" + - "6UklfCpIMfIjf7iGdXKHzg." + - "48V1_ALb6US04U3b." + - "5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6ji" + - "SdiwkIr3ajwQzaBtQD_A." + - "XFBoMYUZodetZdvTiFvSkQ"; - - private String RSAkid = "rsa321"; - private JWK RSAjwk = new RSAKey(new Base64URL("oahUIoWw0K0usKNuOR6H4wkf4oBUXHTxRvgb48E-BVvxkeDNjbC4he8rUW" + - "cJoZmds2h7M70imEVhRU5djINXtqllXI4DFqcI1DgjT9LewND8MW2Krf3S" + - "psk_ZkoFnilakGygTwpZ3uesH-PFABNIUYpOiN15dsQRkgr0vEhxN92i2a" + - "sbOenSZeyaxziK72UwxrrKoExv6kc5twXTq4h-QChLOln0_mtUZwfsRaMS" + - "tPs6mS6XrgxnxbWhojf663tuEQueGC-FCMfra36C9knDFGzKsNa7LZK2dj" + - "YgyD3JR_MB_4NUJW_TqOQtwHYbxevoJArm-L5StowjzGy-_bq6Gw"), // n - new Base64URL("AQAB"), // e - new Base64URL("kLdtIj6GbDks_ApCSTYQtelcNttlKiOyPzMrXHeI-yk1F7-kpDxY4-WY5N" + - "WV5KntaEeXS1j82E375xxhWMHXyvjYecPT9fpwR_M9gV8n9Hrh2anTpTD9" + - "3Dt62ypW3yDsJzBnTnrYu1iwWRgBKrEYY46qAZIrA2xAwnm2X7uGR1hghk" + - "qDp0Vqj3kbSCz1XyfCs6_LehBwtxHIyh8Ripy40p24moOAbgxVw3rxT_vl" + - "t3UVe4WO3JkJOzlpUf-KTVI2Ptgm-dARxTEtE-id-4OJr0h-K-VFs3VSnd" + - "VTIznSxfyrj8ILL6MG_Uv8YAu7VILSB3lOW085-4qE3DzgrTjgyQ"), // d - Use.ENCRYPTION, JWEAlgorithm.RSA_OAEP, RSAkid, null, null, null); - - // AES key wrap not yet tested - // private String AESkid = "aes123"; - // private JWK AESjwk = new OctetSequenceKey(new Base64URL("GawgguFyGrWKav7AX4VKUg"), Use.ENCRYPTION, JWEAlgorithm.A128KW, AESkid); - // - // private Map keys = new ImmutableMap.Builder(). - // put(RSAkid, RSAjwk).put(AESkid, AESjwk).build(); - - private Map keys = new ImmutableMap.Builder(). - put(RSAkid, RSAjwk).build(); - - private DefaultJwtEncryptionAndDecryptionService service; - - @Before - public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException, JOSEException { - - service = new DefaultJwtEncryptionAndDecryptionService(keys); - - claimsSet.setIssuer(issuer); - claimsSet.setSubject(subject); - } - - @Test - public void decrypt_RSA() throws ParseException { - - service.setDefaultDecryptionKeyId(RSAkid); - service.setDefaultEncryptionKeyId(RSAkid); - - JWEObject jwt = JWEObject.parse(compactSerializedJwe); - - assertThat(jwt.getPayload(), nullValue()); // observe..nothing is there - - service.decryptJwt(jwt); - String result = jwt.getPayload().toString(); // and voila! decrypto-magic - - assertEquals(plainText, result); - } - - @Test - public void encryptThenDecrypt_RSA() throws ParseException { - - service.setDefaultDecryptionKeyId(RSAkid); - service.setDefaultEncryptionKeyId(RSAkid); - - JWEHeader header = new JWEHeader(JWEAlgorithm.RSA_OAEP, EncryptionMethod.A256GCM); - - EncryptedJWT jwt = new EncryptedJWT(header, claimsSet); - - service.encryptJwt(jwt); - String serialized = jwt.serialize(); - - EncryptedJWT encryptedJwt = EncryptedJWT.parse(serialized); - assertThat(encryptedJwt.getJWTClaimsSet(), nullValue()); - service.decryptJwt(encryptedJwt); - - ReadOnlyJWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); - - assertEquals(claimsSet.getIssuer(), resultClaims.getIssuer()); - assertEquals(claimsSet.getSubject(), resultClaims.getSubject()); - } - -} diff --git a/openid-connect-common/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java b/openid-connect-common/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java index 38db8f2585..cfcd29d9fc 100644 --- a/openid-connect-common/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java +++ b/openid-connect-common/src/test/java/org/mitre/oauth2/model/ClientDetailsEntityTest.java @@ -1,26 +1,25 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; -import static org.junit.Assert.assertEquals; - import java.util.Date; import org.junit.Test; @@ -29,6 +28,8 @@ import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JWEAlgorithm; +import static org.junit.Assert.assertEquals; + /** * @author jricher * diff --git a/openid-connect-common/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java b/openid-connect-common/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java index 0eaaecc20a..d973fc020e 100644 --- a/openid-connect-common/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java +++ b/openid-connect-common/src/test/java/org/mitre/oauth2/model/RegisteredClientTest.java @@ -1,26 +1,25 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; -import static org.junit.Assert.assertEquals; - import java.sql.Date; import org.junit.Test; @@ -29,6 +28,8 @@ import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JWEAlgorithm; +import static org.junit.Assert.assertEquals; + /** * @author jricher * diff --git a/openid-connect-common/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java b/openid-connect-common/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java index c922544f38..ab19f8b0ab 100644 --- a/openid-connect-common/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java +++ b/openid-connect-common/src/test/java/org/mitre/openid/connect/ClientDetailsEntityJsonProcessorTest.java @@ -1,27 +1,25 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - import java.sql.Date; import org.junit.Test; @@ -34,6 +32,9 @@ import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.JWEAlgorithm; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + /** * @author jricher * diff --git a/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java b/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java index 261cd01d8e..639b295d1d 100644 --- a/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java +++ b/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ConfigurationPropertiesBeanTest.java @@ -1,27 +1,30 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.config; -import static org.junit.Assert.assertEquals; - import org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** * @author jricher @@ -45,11 +48,101 @@ public void testConfigurationPropertiesBean() { bean.setIssuer(iss); bean.setTopbarTitle(title); bean.setLogoImageUrl(logoUrl); + bean.setForceHttps(true); assertEquals(iss, bean.getIssuer()); assertEquals(title, bean.getTopbarTitle()); assertEquals(logoUrl, bean.getLogoImageUrl()); + assertEquals(true, bean.isForceHttps()); + } + + @Test + public void testCheckForHttpsIssuerHttpDefaultFlag() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + + // issuer is http + // leave as default, which is unset/false + try { + bean.setIssuer("http://localhost:8080/openid-connect-server/"); + bean.checkConfigConsistency(); + } catch (BeanCreationException e) { + fail("Unexpected BeanCreationException for http issuer with default forceHttps, message:" + e.getMessage()); + } + } + + @Test + public void testCheckForHttpsIssuerHttpFalseFlag() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + // issuer is http + // set to false + try { + bean.setIssuer("http://localhost:8080/openid-connect-server/"); + bean.setForceHttps(false); + bean.checkConfigConsistency(); + } catch (BeanCreationException e) { + fail("Unexpected BeanCreationException for http issuer with forceHttps=false, message:" + e.getMessage()); + } + } + @Test(expected = BeanCreationException.class) + public void testCheckForHttpsIssuerHttpTrueFlag() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + // issuer is http + // set to true + bean.setIssuer("http://localhost:8080/openid-connect-server/"); + bean.setForceHttps(true); + bean.checkConfigConsistency(); + } + + @Test + public void testCheckForHttpsIssuerHttpsDefaultFlag() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + // issuer is https + // leave as default, which is unset/false + try { + bean.setIssuer("https://localhost:8080/openid-connect-server/"); + bean.checkConfigConsistency(); + } catch (BeanCreationException e) { + fail("Unexpected BeanCreationException for https issuer with default forceHttps, message:" + e.getMessage()); + } + } + + @Test + public void testCheckForHttpsIssuerHttpsFalseFlag() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + // issuer is https + // set to false + try { + bean.setIssuer("https://localhost:8080/openid-connect-server/"); + bean.setForceHttps(false); + bean.checkConfigConsistency(); + } catch (BeanCreationException e) { + fail("Unexpected BeanCreationException for https issuer with forceHttps=false, message:" + e.getMessage()); + } + } + + @Test + public void testCheckForHttpsIssuerHttpsTrueFlag() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + // issuer is https + // set to true + try { + bean.setIssuer("https://localhost:8080/openid-connect-server/"); + bean.setForceHttps(true); + bean.checkConfigConsistency(); + } catch (BeanCreationException e) { + fail("Unexpected BeanCreationException for https issuer with forceHttps=true, message:" + e.getMessage()); + } + + } + + @Test + public void testShortTopbarTitle() { + ConfigurationPropertiesBean bean = new ConfigurationPropertiesBean(); + bean.setTopbarTitle("LONG"); + assertEquals("LONG", bean.getShortTopbarTitle()); + bean.setShortTopbarTitle("SHORT"); + assertEquals("SHORT", bean.getShortTopbarTitle()); } } diff --git a/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java b/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java index 85f362a9a1..7e7513e087 100644 --- a/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java +++ b/openid-connect-common/src/test/java/org/mitre/openid/connect/config/ServerConfigurationTest.java @@ -1,29 +1,30 @@ /******************************************************************************* - * Copyright 2014 The MITRE Corporation - * and the MIT Kerberos and Internet Trust Consortium - * + * Copyright 2018 The MIT Internet Trust Consortium + * + * Portions copyright 2011-2013 The MITRE Corporation + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - ******************************************************************************/ + *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.config; +import org.junit.Test; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import org.junit.Test; - /** * @author jricher * diff --git a/openid-connect-server-webapp/pom.xml b/openid-connect-server-webapp/pom.xml index 5aa3a61757..a294b1d8bb 100644 --- a/openid-connect-server-webapp/pom.xml +++ b/openid-connect-server-webapp/pom.xml @@ -1,153 +1,152 @@ + - 4.0.0 - - org.mitre - openid-connect-parent - 1.1.2-SNAPSHOT - - openid-connect-server-webapp - war - OpenID Connect Server Webapp - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${java-version} - ${java-version} - - - - org.apache.maven.plugins - maven-war-plugin - - openid-connect-server-webapp - - gif - ico - jpg - png - pdf - + 4.0.0 + + org.mitre + openid-connect-parent + 1.3.5-SNAPSHOT + + openid-connect-server-webapp + war + OpenID Connect Server Webapp + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java-version} + ${java-version} + + + + org.apache.maven.plugins + maven-war-plugin + + openid-connect-server-webapp src/main/webapp true + + **/*.tag + **/*.jsp + + + + src/main/webapp + false + + **/*.tag + **/*.jsp + - - - - org.apache.maven.plugins - maven-dependency-plugin - - - install - install - - sources - - - - - - org.eclipse.jetty - jetty-maven-plugin - - - /openid-connect-server-webapp - - - - - - - - org.mitre - openid-connect-server - 1.1.2-SNAPSHOT - - - org.springframework - spring-orm - ${org.springframework-version} - - - - commons-logging - commons-logging - - - - - org.slf4j - jcl-over-slf4j - ${org.slf4j-version} - runtime - - - org.slf4j - slf4j-log4j12 - ${org.slf4j-version} - runtime - - - log4j - log4j - 1.2.15 - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - runtime - - - commons-dbcp - commons-dbcp - 1.4 - + less/** + + + + org.apache.maven.plugins + maven-dependency-plugin + + + install + install + + sources + + + + + + org.eclipse.jetty + jetty-maven-plugin + + ${project.build.directory}/openid-connect-server-webapp.war + + /openid-connect-server-webapp + + + + + ro.isdc.wro4j + wro4j-maven-plugin + + bootstrap,bootstrap-responsive + ${project.build.directory}/${project.build.finalName} + ${project.build.directory}/${project.build.finalName}/resources/bootstrap2/css/ + ${project.build.directory}/${project.build.finalName}/js/ + ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory + + + + + + + + org.mitre + openid-connect-server + + + org.springframework + spring-orm + + + commons-logging + commons-logging + + + + + org.slf4j + jcl-over-slf4j + + + org.slf4j + slf4j-log4j12 + + + log4j + log4j + org.hsqldb hsqldb - 2.2.9 - - org.eclipse.persistence - org.eclipse.persistence.jpa - 2.4.0 - - - org.springframework.security - spring-security-taglibs - ${spring.security.version} - - - org.springframework - * - - - - - javax.servlet - jstl - 1.2 - + + org.eclipse.persistence + org.eclipse.persistence.jpa + + + org.springframework.security + spring-security-taglibs + + + javax.servlet + jstl + - + + com.zaxxer + HikariCP + + + Deployable package of the OpenID Connect server diff --git a/openid-connect-server-webapp/src/main/resources/db/clients.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/clients.sql similarity index 98% rename from openid-connect-server-webapp/src/main/resources/db/clients.sql rename to openid-connect-server-webapp/src/main/resources/db/hsql/clients.sql index 7e7a8a53d9..1410f7bd15 100644 --- a/openid-connect-server-webapp/src/main/resources/db/clients.sql +++ b/openid-connect-server-webapp/src/main/resources/db/hsql/clients.sql @@ -28,6 +28,7 @@ INSERT INTO client_redirect_uri_TEMP (owner_id, redirect_uri) VALUES INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('client', 'authorization_code'), ('client', 'urn:ietf:params:oauth:grant_type:redelegate'), + ('client', 'urn:ietf:params:oauth:grant-type:device_code'), ('client', 'implicit'), ('client', 'refresh_token'); diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql new file mode 100644 index 0000000000..38636a96f9 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_index.sql @@ -0,0 +1,19 @@ +-- +-- Indexes for HSQLDB +-- + +CREATE INDEX IF NOT EXISTS at_tv_idx ON access_token(token_value); +CREATE INDEX IF NOT EXISTS ts_oi_idx ON token_scope(owner_id); +CREATE INDEX IF NOT EXISTS at_exp_idx ON access_token(expiration); +CREATE INDEX IF NOT EXISTS rf_ahi_idx ON refresh_token(auth_holder_id); +CREATE INDEX IF NOT EXISTS rf_tv_idx ON refresh_token(token_value); +CREATE INDEX IF NOT EXISTS cd_ci_idx ON client_details(client_id); +CREATE INDEX IF NOT EXISTS at_ahi_idx ON access_token(auth_holder_id); +CREATE INDEX IF NOT EXISTS aha_oi_idx ON authentication_holder_authority(owner_id); +CREATE INDEX IF NOT EXISTS ahe_oi_idx ON authentication_holder_extension(owner_id); +CREATE INDEX IF NOT EXISTS ahrp_oi_idx ON authentication_holder_request_parameter(owner_id); +CREATE INDEX IF NOT EXISTS ahri_oi_idx ON authentication_holder_resource_id(owner_id); +CREATE INDEX IF NOT EXISTS ahrt_oi_idx ON authentication_holder_response_type(owner_id); +CREATE INDEX IF NOT EXISTS ahs_oi_idx ON authentication_holder_scope(owner_id); +CREATE INDEX IF NOT EXISTS ac_ahi_idx ON authorization_code(auth_holder_id); +CREATE INDEX IF NOT EXISTS suaa_oi_idx ON saved_user_auth_authority(owner_id); diff --git a/openid-connect-server-webapp/src/main/resources/db/tables/hsql_database_tables.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql similarity index 53% rename from openid-connect-server-webapp/src/main/resources/db/tables/hsql_database_tables.sql rename to openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql index fe3cf32c0b..2a01756298 100644 --- a/openid-connect-server-webapp/src/main/resources/db/tables/hsql_database_tables.sql +++ b/openid-connect-server-webapp/src/main/resources/db/hsql/hsql_database_tables.sql @@ -8,10 +8,15 @@ CREATE TABLE IF NOT EXISTS access_token ( expiration TIMESTAMP, token_type VARCHAR(256), refresh_token_id BIGINT, - client_id VARCHAR(256), + client_id BIGINT, auth_holder_id BIGINT, - id_token_id BIGINT, - approved_site_id BIGINT + approved_site_id BIGINT, + UNIQUE(token_value) +); + +CREATE TABLE IF NOT EXISTS access_token_permissions ( + access_token_id BIGINT NOT NULL, + permission_id BIGINT NOT NULL ); CREATE TABLE IF NOT EXISTS address ( @@ -26,12 +31,12 @@ CREATE TABLE IF NOT EXISTS address ( CREATE TABLE IF NOT EXISTS approved_site ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, - user_id VARCHAR(4096), - client_id VARCHAR(4096), + user_id VARCHAR(256), + client_id VARCHAR(256), creation_date TIMESTAMP, access_date TIMESTAMP, timeout_date TIMESTAMP, - whitelisted_site_id VARCHAR(256) + whitelisted_site_id BIGINT ); CREATE TABLE IF NOT EXISTS approved_site_scope ( @@ -41,19 +46,66 @@ CREATE TABLE IF NOT EXISTS approved_site_scope ( CREATE TABLE IF NOT EXISTS authentication_holder ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + user_auth_id BIGINT, + approved BOOLEAN, + redirect_uri VARCHAR(2048), + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_authority ( + owner_id BIGINT, + authority VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_resource_id ( + owner_id BIGINT, + resource_id VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_response_type ( + owner_id BIGINT, + response_type VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_extension ( owner_id BIGINT, - authentication LONGVARBINARY + extension VARCHAR(2048), + val VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_scope ( + owner_id BIGINT, + scope VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter ( + owner_id BIGINT, + param VARCHAR(2048), + val VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS saved_user_auth ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + name VARCHAR(1024), + authenticated BOOLEAN, + source_class VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS saved_user_auth_authority ( + owner_id BIGINT, + authority VARCHAR(256) ); CREATE TABLE IF NOT EXISTS client_authority ( owner_id BIGINT, - authority LONGVARBINARY + authority VARCHAR(256) ); CREATE TABLE IF NOT EXISTS authorization_code ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, code VARCHAR(256), - authentication LONGVARBINARY + auth_holder_id BIGINT, + expiration TIMESTAMP ); CREATE TABLE IF NOT EXISTS client_grant_type ( @@ -79,6 +131,7 @@ CREATE TABLE IF NOT EXISTS client_details ( dynamically_registered BOOLEAN DEFAULT false NOT NULL, allow_introspection BOOLEAN DEFAULT false NOT NULL, id_token_validity_seconds BIGINT DEFAULT 600 NOT NULL, + device_code_validity_seconds BIGINT, client_id VARCHAR(256), client_secret VARCHAR(2048), @@ -96,6 +149,7 @@ CREATE TABLE IF NOT EXISTS client_details ( tos_uri VARCHAR(2048), jwks_uri VARCHAR(2048), + jwks VARCHAR(8192), sector_identifier_uri VARCHAR(2048), request_object_signing_alg VARCHAR(256), @@ -114,7 +168,14 @@ CREATE TABLE IF NOT EXISTS client_details ( require_auth_time BOOLEAN, created_at TIMESTAMP, initiate_login_uri VARCHAR(2048), - post_logout_redirect_uri VARCHAR(2048), + clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL, + + software_statement VARCHAR(4096), + software_id VARCHAR(2048), + software_version VARCHAR(2048), + + code_challenge_method VARCHAR(256), + UNIQUE (client_id) ); @@ -123,6 +184,11 @@ CREATE TABLE IF NOT EXISTS client_request_uri ( request_uri VARCHAR(2000) ); +CREATE TABLE IF NOT EXISTS client_post_logout_redirect_uri ( + owner_id BIGINT, + post_logout_redirect_uri VARCHAR(2000) +); + CREATE TABLE IF NOT EXISTS client_default_acr_value ( owner_id BIGINT, default_acr_value VARCHAR(2000) @@ -138,12 +204,17 @@ CREATE TABLE IF NOT EXISTS client_redirect_uri ( redirect_uri VARCHAR(2048) ); +CREATE TABLE IF NOT EXISTS client_claims_redirect_uri ( + owner_id BIGINT, + redirect_uri VARCHAR(2048) +); + CREATE TABLE IF NOT EXISTS refresh_token ( id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, token_value VARCHAR(4096), expiration TIMESTAMP, auth_holder_id BIGINT, - client_id VARCHAR(256) + client_id BIGINT ); CREATE TABLE IF NOT EXISTS client_resource ( @@ -166,10 +237,8 @@ CREATE TABLE IF NOT EXISTS system_scope ( scope VARCHAR(256) NOT NULL, description VARCHAR(4096), icon VARCHAR(256), - allow_dyn_reg BOOLEAN DEFAULT false NOT NULL, + restricted BOOLEAN DEFAULT false NOT NULL, default_scope BOOLEAN DEFAULT false NOT NULL, - structured BOOLEAN DEFAULT false NOT NULL, - structured_param_description VARCHAR(256), UNIQUE (scope) ); @@ -194,7 +263,8 @@ CREATE TABLE IF NOT EXISTS user_info ( phone_number_verified BOOLEAN, address_id VARCHAR(256), updated_time VARCHAR(256), - birthdate VARCHAR(256) + birthdate VARCHAR(256), + src VARCHAR(4096) ); CREATE TABLE IF NOT EXISTS whitelisted_site ( @@ -214,3 +284,101 @@ CREATE TABLE IF NOT EXISTS pairwise_identifier ( sub VARCHAR(256), sector_identifier VARCHAR(2048) ); + +CREATE TABLE IF NOT EXISTS resource_set ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + name VARCHAR(1024) NOT NULL, + uri VARCHAR(1024), + icon_uri VARCHAR(1024), + rs_type VARCHAR(256), + owner VARCHAR(256) NOT NULL, + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS resource_set_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS permission_ticket ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + ticket VARCHAR(256) NOT NULL, + permission_id BIGINT NOT NULL, + expiration TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS permission ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + resource_set_id BIGINT +); + +CREATE TABLE IF NOT EXISTS permission_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + name VARCHAR(256), + friendly_name VARCHAR(1024), + claim_type VARCHAR(1024), + claim_value VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS claim_to_policy ( + policy_id BIGINT NOT NULL, + claim_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim_to_permission_ticket ( + permission_ticket_id BIGINT NOT NULL, + claim_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS policy ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + name VARCHAR(1024), + resource_set_id BIGINT +); + +CREATE TABLE IF NOT EXISTS policy_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim_token_format ( + owner_id BIGINT NOT NULL, + claim_token_format VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS claim_issuer ( + owner_id BIGINT NOT NULL, + issuer VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS saved_registered_client ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + issuer VARCHAR(1024), + registered_client VARCHAR(8192) +); + +CREATE TABLE IF NOT EXISTS device_code ( + id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, + device_code VARCHAR(1024), + user_code VARCHAR(1024), + expiration TIMESTAMP, + client_id VARCHAR(256), + approved BOOLEAN, + auth_holder_id BIGINT +); + +CREATE TABLE IF NOT EXISTS device_code_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS device_code_request_parameter ( + owner_id BIGINT, + param VARCHAR(2048), + val VARCHAR(2048) +); diff --git a/openid-connect-server-webapp/src/main/resources/db/tables/loading_temp_tables.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql similarity index 90% rename from openid-connect-server-webapp/src/main/resources/db/tables/loading_temp_tables.sql rename to openid-connect-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql index c1e7465306..37b0092e75 100644 --- a/openid-connect-server-webapp/src/main/resources/db/tables/loading_temp_tables.sql +++ b/openid-connect-server-webapp/src/main/resources/db/hsql/loading_temp_tables.sql @@ -14,8 +14,7 @@ CREATE TEMPORARY TABLE IF NOT EXISTS users_TEMP ( enabled boolean not null); CREATE TEMPORARY TABLE IF NOT EXISTS user_info_TEMP ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, - sub VARCHAR(256), + sub VARCHAR(256) not null primary key, preferred_username VARCHAR(256), name VARCHAR(256), given_name VARCHAR(256), @@ -69,8 +68,6 @@ CREATE TEMPORARY TABLE IF NOT EXISTS system_scope_TEMP ( scope VARCHAR(256), description VARCHAR(4096), icon VARCHAR(256), - allow_dyn_reg BOOLEAN, - default_scope BOOLEAN, - structured BOOLEAN, - structured_param_description VARCHAR(256) + restricted BOOLEAN, + default_scope BOOLEAN ); \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/db/hsql/scopes.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/scopes.sql new file mode 100644 index 0000000000..8e72c88c7f --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/hsql/scopes.sql @@ -0,0 +1,33 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +SET AUTOCOMMIT FALSE; + +START TRANSACTION; + +-- +-- Insert scope information into the temporary tables. +-- + +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('openid', 'log in using your identity', 'user', false, true), + ('profile', 'basic profile information', 'list-alt', false, true), + ('email', 'email address', 'envelope', false, true), + ('address', 'physical address', 'home', false, true), + ('phone', 'telephone number', 'bell', false, true), + ('offline_access', 'offline access', 'time', false, false); + +-- +-- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store. +-- + +MERGE INTO system_scope + USING (SELECT scope, description, icon, restricted, default_scope FROM system_scope_TEMP) AS vals(scope, description, icon, restricted, default_scope) + ON vals.scope = system_scope.scope + WHEN NOT MATCHED THEN + INSERT (scope, description, icon, restricted, default_scope) VALUES(vals.scope, vals.description, vals.icon, vals.restricted, vals.default_scope); + +COMMIT; + +SET AUTOCOMMIT TRUE; \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/db/tables/security-schema.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/security-schema.sql similarity index 100% rename from openid-connect-server-webapp/src/main/resources/db/tables/security-schema.sql rename to openid-connect-server-webapp/src/main/resources/db/hsql/security-schema.sql diff --git a/openid-connect-server-webapp/src/main/resources/db/users.sql b/openid-connect-server-webapp/src/main/resources/db/hsql/users.sql similarity index 100% rename from openid-connect-server-webapp/src/main/resources/db/users.sql rename to openid-connect-server-webapp/src/main/resources/db/hsql/users.sql diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/clients.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/clients.sql new file mode 100644 index 0000000000..7f02557899 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/clients.sql @@ -0,0 +1,61 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +SET AUTOCOMMIT = 0; + +START TRANSACTION; + +-- +-- Insert client information into the temporary tables. To add clients to the HSQL database, edit things here. +-- + +INSERT INTO client_details_TEMP (client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection) VALUES + ('client', 'secret', 'Test Client', false, null, 3600, 600, true); + +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES + ('client', 'openid'), + ('client', 'profile'), + ('client', 'email'), + ('client', 'address'), + ('client', 'phone'), + ('client', 'offline_access'); + +INSERT INTO client_redirect_uri_TEMP (owner_id, redirect_uri) VALUES + ('client', 'http://localhost/'), + ('client', 'http://localhost:8080/'); + +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES + ('client', 'authorization_code'), + ('client', 'urn:ietf:params:oauth:grant_type:redelegate'), + ('client', 'implicit'), + ('client', 'refresh_token'); + +-- +-- Merge the temporary clients safely into the database. This is a two-step process to keep clients from being created on every startup with a persistent store. +-- + +INSERT INTO client_details (client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection) + SELECT client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection FROM client_details_TEMP + ON DUPLICATE KEY UPDATE client_details.client_id = client_details.client_id; + +INSERT INTO client_scope (owner_id, scope) + SELECT id, scope FROM client_scope_TEMP, client_details WHERE client_details.client_id = client_scope_TEMP.owner_id + ON DUPLICATE KEY UPDATE client_scope.owner_id = client_scope.owner_id; + +INSERT INTO client_redirect_uri (owner_id, redirect_uri) + SELECT id, redirect_uri FROM client_redirect_uri_TEMP, client_details WHERE client_details.client_id = client_redirect_uri_TEMP.owner_id + ON DUPLICATE KEY UPDATE client_redirect_uri.owner_id = client_redirect_uri.owner_id; + +INSERT INTO client_grant_type (owner_id, grant_type) + SELECT id, grant_type FROM client_grant_type_TEMP, client_details WHERE client_details.client_id = client_grant_type_TEMP.owner_id + ON DUPLICATE KEY UPDATE client_grant_type.owner_id = client_grant_type.owner_id; + +-- +-- Close the transaction and turn autocommit back on +-- + +COMMIT; + +SET AUTOCOMMIT = 1; + diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql new file mode 100644 index 0000000000..f5daf991da --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_index.sql @@ -0,0 +1,19 @@ +-- +-- Indexes for MySQL +-- + +CREATE INDEX at_tv_idx ON access_token(token_value(767)); +CREATE INDEX ts_oi_idx ON token_scope(owner_id); +CREATE INDEX at_exp_idx ON access_token(expiration); +CREATE INDEX rf_ahi_idx ON refresh_token(auth_holder_id); +CREATE INDEX rf_tv_idx ON refresh_token(token_value(105)); +CREATE INDEX cd_ci_idx ON client_details(client_id); +CREATE INDEX at_ahi_idx ON access_token(auth_holder_id); +CREATE INDEX aha_oi_idx ON authentication_holder_authority(owner_id); +CREATE INDEX ahe_oi_idx ON authentication_holder_extension(owner_id); +CREATE INDEX ahrp_oi_idx ON authentication_holder_request_parameter(owner_id); +CREATE INDEX ahri_oi_idx ON authentication_holder_resource_id(owner_id); +CREATE INDEX ahrt_oi_idx ON authentication_holder_response_type(owner_id); +CREATE INDEX ahs_oi_idx ON authentication_holder_scope(owner_id); +CREATE INDEX ac_ahi_idx ON authorization_code(auth_holder_id); +CREATE INDEX suaa_oi_idx ON saved_user_auth_authority(owner_id); diff --git a/openid-connect-server-webapp/src/main/resources/db/tables/mysql_database_tables.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql similarity index 50% rename from openid-connect-server-webapp/src/main/resources/db/tables/mysql_database_tables.sql rename to openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql index 2b34ed9f68..7e00cc8762 100644 --- a/openid-connect-server-webapp/src/main/resources/db/tables/mysql_database_tables.sql +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/mysql_database_tables.sql @@ -8,12 +8,16 @@ CREATE TABLE IF NOT EXISTS access_token ( expiration TIMESTAMP NULL, token_type VARCHAR(256), refresh_token_id BIGINT, - client_id VARCHAR(256), + client_id BIGINT, auth_holder_id BIGINT, - id_token_id BIGINT, approved_site_id BIGINT ); +CREATE TABLE IF NOT EXISTS access_token_permissions ( + access_token_id BIGINT NOT NULL, + permission_id BIGINT NOT NULL +); + CREATE TABLE IF NOT EXISTS address ( id BIGINT AUTO_INCREMENT PRIMARY KEY, formatted VARCHAR(256), @@ -26,12 +30,12 @@ CREATE TABLE IF NOT EXISTS address ( CREATE TABLE IF NOT EXISTS approved_site ( id BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id VARCHAR(4096), - client_id VARCHAR(4096), + user_id VARCHAR(256), + client_id VARCHAR(256), creation_date TIMESTAMP NULL, access_date TIMESTAMP NULL, timeout_date TIMESTAMP NULL, - whitelisted_site_id VARCHAR(256) + whitelisted_site_id BIGINT ); CREATE TABLE IF NOT EXISTS approved_site_scope ( @@ -41,19 +45,66 @@ CREATE TABLE IF NOT EXISTS approved_site_scope ( CREATE TABLE IF NOT EXISTS authentication_holder ( id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_auth_id BIGINT, + approved BOOLEAN, + redirect_uri VARCHAR(2048), + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_authority ( + owner_id BIGINT, + authority VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_resource_id ( + owner_id BIGINT, + resource_id VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_response_type ( + owner_id BIGINT, + response_type VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_extension ( + owner_id BIGINT, + extension VARCHAR(2048), + val VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_scope ( + owner_id BIGINT, + scope VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter ( + owner_id BIGINT, + param VARCHAR(2048), + val VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS saved_user_auth ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(1024), + authenticated BOOLEAN, + source_class VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS saved_user_auth_authority ( owner_id BIGINT, - authentication LONGBLOB + authority VARCHAR(256) ); CREATE TABLE IF NOT EXISTS client_authority ( owner_id BIGINT, - authority LONGBLOB + authority VARCHAR(256) ); CREATE TABLE IF NOT EXISTS authorization_code ( id BIGINT AUTO_INCREMENT PRIMARY KEY, code VARCHAR(256), - authentication LONGBLOB + auth_holder_id BIGINT, + expiration TIMESTAMP NULL ); CREATE TABLE IF NOT EXISTS client_grant_type ( @@ -73,12 +124,13 @@ CREATE TABLE IF NOT EXISTS blacklisted_site ( CREATE TABLE IF NOT EXISTS client_details ( id BIGINT AUTO_INCREMENT PRIMARY KEY, - + client_description VARCHAR(1024), - reuse_refresh_tokens BOOLEAN NOT NULL DEFAULT 1, - dynamically_registered BOOLEAN NOT NULL DEFAULT 0, - allow_introspection BOOLEAN NOT NULL DEFAULT 0, - id_token_validity_seconds BIGINT NOT NULL DEFAULT 600, + reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL, + dynamically_registered BOOLEAN DEFAULT false NOT NULL, + allow_introspection BOOLEAN DEFAULT false NOT NULL, + id_token_validity_seconds BIGINT DEFAULT 600 NOT NULL, + device_code_validity_seconds BIGINT, client_id VARCHAR(256), client_secret VARCHAR(2048), @@ -96,6 +148,7 @@ CREATE TABLE IF NOT EXISTS client_details ( tos_uri VARCHAR(2048), jwks_uri VARCHAR(2048), + jwks VARCHAR(8192), sector_identifier_uri VARCHAR(2048), request_object_signing_alg VARCHAR(256), @@ -114,8 +167,15 @@ CREATE TABLE IF NOT EXISTS client_details ( require_auth_time BOOLEAN, created_at TIMESTAMP NULL, initiate_login_uri VARCHAR(2048), - post_logout_redirect_uri VARCHAR(2048), - unique(client_id) + clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL, + + software_statement VARCHAR(4096), + software_id VARCHAR(2048), + software_version VARCHAR(2048), + + code_challenge_method VARCHAR(256), + + UNIQUE (client_id) ); CREATE TABLE IF NOT EXISTS client_request_uri ( @@ -123,6 +183,11 @@ CREATE TABLE IF NOT EXISTS client_request_uri ( request_uri VARCHAR(2000) ); +CREATE TABLE IF NOT EXISTS client_post_logout_redirect_uri ( + owner_id BIGINT, + post_logout_redirect_uri VARCHAR(2000) +); + CREATE TABLE IF NOT EXISTS client_default_acr_value ( owner_id BIGINT, default_acr_value VARCHAR(2000) @@ -138,12 +203,17 @@ CREATE TABLE IF NOT EXISTS client_redirect_uri ( redirect_uri VARCHAR(2048) ); +CREATE TABLE IF NOT EXISTS client_claims_redirect_uri ( + owner_id BIGINT, + redirect_uri VARCHAR(2048) +); + CREATE TABLE IF NOT EXISTS refresh_token ( id BIGINT AUTO_INCREMENT PRIMARY KEY, token_value VARCHAR(4096), expiration TIMESTAMP NULL, auth_holder_id BIGINT, - client_id VARCHAR(256) + client_id BIGINT ); CREATE TABLE IF NOT EXISTS client_resource ( @@ -166,11 +236,9 @@ CREATE TABLE IF NOT EXISTS system_scope ( scope VARCHAR(256) NOT NULL, description VARCHAR(4096), icon VARCHAR(256), - allow_dyn_reg BOOLEAN NOT NULL DEFAULT 0, - default_scope BOOLEAN NOT NULL DEFAULT 0, - structured BOOLEAN NOT NULL DEFAULT 0, - structured_param_description VARCHAR(256), - unique(scope) + restricted BOOLEAN DEFAULT false NOT NULL, + default_scope BOOLEAN DEFAULT false NOT NULL, + UNIQUE (scope) ); CREATE TABLE IF NOT EXISTS user_info ( @@ -194,7 +262,8 @@ CREATE TABLE IF NOT EXISTS user_info ( phone_number_verified BOOLEAN, address_id VARCHAR(256), updated_time VARCHAR(256), - birthdate VARCHAR(256) + birthdate VARCHAR(256), + src VARCHAR(4096) ); CREATE TABLE IF NOT EXISTS whitelisted_site ( @@ -214,3 +283,101 @@ CREATE TABLE IF NOT EXISTS pairwise_identifier ( sub VARCHAR(256), sector_identifier VARCHAR(2048) ); + +CREATE TABLE IF NOT EXISTS resource_set ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(1024) NOT NULL, + uri VARCHAR(1024), + icon_uri VARCHAR(1024), + rs_type VARCHAR(256), + owner VARCHAR(256) NOT NULL, + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS resource_set_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS permission_ticket ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + ticket VARCHAR(256) NOT NULL, + permission_id BIGINT NOT NULL, + expiration TIMESTAMP NULL +); + +CREATE TABLE IF NOT EXISTS permission ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + resource_set_id BIGINT +); + +CREATE TABLE IF NOT EXISTS permission_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(256), + friendly_name VARCHAR(1024), + claim_type VARCHAR(1024), + claim_value VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS claim_to_policy ( + policy_id BIGINT NOT NULL, + claim_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim_to_permission_ticket ( + permission_ticket_id BIGINT NOT NULL, + claim_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS policy ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(1024), + resource_set_id BIGINT +); + +CREATE TABLE IF NOT EXISTS policy_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim_token_format ( + owner_id BIGINT NOT NULL, + claim_token_format VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS claim_issuer ( + owner_id BIGINT NOT NULL, + issuer VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS saved_registered_client ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + issuer VARCHAR(1024), + registered_client VARCHAR(8192) +); + +CREATE TABLE IF NOT EXISTS device_code ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + device_code VARCHAR(1024), + user_code VARCHAR(1024), + expiration TIMESTAMP NULL, + client_id VARCHAR(256), + approved BOOLEAN, + auth_holder_id BIGINT +); + +CREATE TABLE IF NOT EXISTS device_code_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS device_code_request_parameter ( + owner_id BIGINT, + param VARCHAR(2048), + val VARCHAR(2048) +); diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/scopes.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/scopes.sql new file mode 100644 index 0000000000..3768977ec1 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/scopes.sql @@ -0,0 +1,31 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +SET AUTOCOMMIT = 0; + +START TRANSACTION; + +-- +-- Insert scope information into the temporary tables. +-- + +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('openid', 'log in using your identity', 'user', false, true), + ('profile', 'basic profile information', 'list-alt', false, true), + ('email', 'email address', 'envelope', false, true), + ('address', 'physical address', 'home', false, true), + ('phone', 'telephone number', 'bell', false, true), + ('offline_access', 'offline access', 'time', false, false); + +-- +-- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store. +-- + +INSERT INTO system_scope (scope, description, icon, restricted, default_scope, structured, structured_param_description) + SELECT scope, description, icon, restricted, default_scope, structured, structured_param_description FROM system_scope_TEMP + ON DUPLICATE KEY UPDATE system_scope.scope = system_scope.scope; + +COMMIT; + +SET AUTOCOMMIT = 1; diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/security-schema.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/security-schema.sql new file mode 100644 index 0000000000..bc5d70b880 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/security-schema.sql @@ -0,0 +1,14 @@ +-- +-- Tables for Spring Security's user details service +-- + +create table IF NOT EXISTS users( + username varchar(50) not null primary key, + password varchar(50) not null, + enabled boolean not null); + + create table IF NOT EXISTS authorities ( + username varchar(50) not null, + authority varchar(50) not null, + constraint fk_authorities_users foreign key(username) references users(username), + constraint ix_authority unique (username,authority)); \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/db/mysql/users.sql b/openid-connect-server-webapp/src/main/resources/db/mysql/users.sql new file mode 100644 index 0000000000..fc82e48006 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/mysql/users.sql @@ -0,0 +1,52 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +SET AUTOCOMMIT = 0; + +START TRANSACTION; + +-- +-- Insert user information into the temporary tables. To add users to the HSQL database, edit things here. +-- + +INSERT INTO users_TEMP (username, password, enabled) VALUES + ('admin','password',true), + ('user','password',true); + + +INSERT INTO authorities_TEMP (username, authority) VALUES + ('admin','ROLE_ADMIN'), + ('admin','ROLE_USER'), + ('user','ROLE_USER'); + +-- By default, the username column here has to match the username column in the users table, above +INSERT INTO user_info_TEMP (sub, preferred_username, name, email, email_verified) VALUES + ('90342.ASDFJWFA','admin','Demo Admin','admin@example.com', true), + ('01921.FLANRJQW','user','Demo User','user@example.com', true); + + +-- +-- Merge the temporary users safely into the database. This is a two-step process to keep users from being created on every startup with a persistent store. +-- + +INSERT INTO users (username, password, enabled) + SELECT username, password, enabled FROM users_TEMP + ON DUPLICATE KEY UPDATE users.username = users.username; + +INSERT INTO authorities (username,authority) + SELECT username, authority FROM authorities_TEMP + ON DUPLICATE KEY UPDATE authorities.username = authorities.username; + +INSERT INTO user_info (sub, preferred_username, name, email, email_verified) + SELECT sub, preferred_username, name, email, email_verified FROM user_info_TEMP + ON DUPLICATE KEY UPDATE user_info.preferred_username = user_info.preferred_username; + +-- +-- Close the transaction and turn autocommit back on +-- + +COMMIT; + +SET AUTOCOMMIT = 1; + diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/clients_oracle.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/clients_oracle.sql new file mode 100644 index 0000000000..488d928457 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/clients_oracle.sql @@ -0,0 +1,51 @@ +-- +-- Insert client information into the temporary tables. To add clients to the Oracle database, edit things here. +-- + +INSERT INTO client_details_TEMP (client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection) VALUES + ('client', 'secret', 'Test Client', 0, null, 3600, 600, 1); + +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('client', 'openid'); +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('client', 'profile'); +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('client', 'email'); +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('client', 'address'); +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('client', 'phone'); +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('client', 'offline_access'); + +INSERT INTO client_redirect_uri_TEMP (owner_id, redirect_uri) VALUES ('client', 'http://localhost/'); +INSERT INTO client_redirect_uri_TEMP (owner_id, redirect_uri) VALUES ('client', 'http://localhost:8080/'); + +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('client', 'authorization_code'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('client', 'urn:ietf:params:oauth:grant_type:redelegate'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('client', 'implicit'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('client', 'refresh_token'); + +-- +-- Merge the temporary clients safely into the database. This is a two-step process to keep clients from being created on every startup with a persistent store. +-- + +MERGE INTO client_details + USING (SELECT client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection FROM client_details_TEMP) vals + ON (vals.client_id = client_details.client_id) + WHEN NOT MATCHED THEN + INSERT (id, client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, + id_token_validity_seconds, allow_introspection) VALUES(client_details_seq.nextval, vals.client_id, vals.client_secret, vals.client_name, vals.dynamically_registered, + vals.refresh_token_validity_seconds, vals.access_token_validity_seconds, vals.id_token_validity_seconds, vals.allow_introspection); + +MERGE INTO client_scope + USING (SELECT id, scope FROM client_scope_TEMP, client_details WHERE client_details.client_id = client_scope_TEMP.owner_id) vals + ON (vals.id = client_scope.owner_id AND vals.scope = client_scope.scope) + WHEN NOT MATCHED THEN + INSERT (owner_id, scope) values (vals.id, vals.scope); + +MERGE INTO client_redirect_uri + USING (SELECT id, redirect_uri FROM client_redirect_uri_TEMP, client_details WHERE client_details.client_id = client_redirect_uri_TEMP.owner_id) vals + ON (vals.id = client_redirect_uri.owner_id AND vals.redirect_uri = client_redirect_uri.redirect_uri) + WHEN NOT MATCHED THEN + INSERT (owner_id, redirect_uri) values (vals.id, vals.redirect_uri); + +MERGE INTO client_grant_type + USING (SELECT id, grant_type FROM client_grant_type_TEMP, client_details WHERE client_details.client_id = client_grant_type_TEMP.owner_id) vals + ON (vals.id = client_grant_type.owner_id AND vals.grant_type = client_grant_type.grant_type) + WHEN NOT MATCHED THEN + INSERT (owner_id, grant_type) values (vals.id, vals.grant_type); diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/create_db-user b/openid-connect-server-webapp/src/main/resources/db/oracle/create_db-user new file mode 100644 index 0000000000..fdbf9d44fb --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/create_db-user @@ -0,0 +1,15 @@ +drop user oauth cascade; +drop tablespace data_ts INCLUDING CONTENTS AND DATAFILES; +drop tablespace temp_ts INCLUDING CONTENTS AND DATAFILES; +CREATE TABLESPACE data_ts DATAFILE 'data_ts.dat' SIZE 40M ONLINE; +CREATE TEMPORARY TABLESPACE temp_ts TEMPFILE 'temp_ts.dbf' SIZE 5M AUTOEXTEND ON; +create user oauth identified by test DEFAULT TABLESPACE data_ts QUOTA 500K ON data_ts TEMPORARY TABLESPACE temp_ts; +GRANT CONNECT TO oauth; +GRANT UNLIMITED TABLESPACE TO oauth; +grant create session to oauth; +grant create table to oauth; +GRANT CREATE TABLESPACE TO oauth; +GRANT CREATE VIEW TO oauth; +GRANT CREATE ANY INDEX TO oauth; +GRANT CREATE SEQUENCE TO oauth; +GRANT CREATE SYNONYM TO oauth; diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml b/openid-connect-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml new file mode 100644 index 0000000000..2aba62824f --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/entity-mappings_oracle.xml @@ -0,0 +1,281 @@ + + + + OpenID Connect Server entities + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql new file mode 100644 index 0000000000..c9a1e7f3d6 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/loading_temp_tables_oracle.sql @@ -0,0 +1,77 @@ +-- +-- Temporary tables used during the bootstrapping process to safely load users and clients. +-- These are not needed if you're not using the users.sql/clients.sql files to bootstrap the database. +-- + +CREATE GLOBAL TEMPORARY TABLE authorities_TEMP ( + username varchar2(50) not null, + authority varchar2(50) not null, + constraint ix_authority_TEMP unique (username,authority) +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE users_TEMP ( + username VARCHAR2(50) not null primary key, + password VARCHAR2(50) not null, + enabled NUMBER(1) not null +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE user_info_TEMP ( + sub VARCHAR2(256) not null primary key, + preferred_username VARCHAR2(256), + name VARCHAR2(256), + given_name VARCHAR2(256), + family_name VARCHAR2(256), + middle_name VARCHAR2(256), + nickname VARCHAR2(256), + profile VARCHAR2(256), + picture VARCHAR2(256), + website VARCHAR2(256), + email VARCHAR2(256), + email_verified NUMBER(1), + gender VARCHAR2(256), + zone_info VARCHAR2(256), + locale VARCHAR2(256), + phone_number VARCHAR2(256), + address_id VARCHAR2(256), + updated_time VARCHAR2(256), + birthdate VARCHAR2(256) +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE client_details_TEMP ( + client_description VARCHAR2(256), + dynamically_registered NUMBER(1), + id_token_validity_seconds NUMBER(19), + + client_id VARCHAR2(256), + client_secret VARCHAR2(2048), + access_token_validity_seconds NUMBER(19), + refresh_token_validity_seconds NUMBER(19), + allow_introspection NUMBER(1), + + client_name VARCHAR2(256) +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE client_scope_TEMP ( + owner_id VARCHAR2(256), + scope VARCHAR2(2048) +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE client_redirect_uri_TEMP ( + owner_id VARCHAR2(256), + redirect_uri VARCHAR2(2048) +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE client_grant_type_TEMP ( + owner_id VARCHAR2(256), + grant_type VARCHAR2(2000) +) ON COMMIT PRESERVE ROWS; + +CREATE GLOBAL TEMPORARY TABLE system_scope_TEMP ( + scope VARCHAR2(256), + description VARCHAR2(4000), + icon VARCHAR2(256), + restricted NUMBER(1), + default_scope NUMBER(1), + structured NUMBER(1), + structured_param_description VARCHAR2(256) +) ON COMMIT PRESERVE ROWS; diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql new file mode 100644 index 0000000000..fc70a7ae41 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_index.sql @@ -0,0 +1,18 @@ +-- +-- Indexes for Oracle +-- + +CREATE INDEX at_tv_idx ON access_token(token_value); +CREATE INDEX ts_oi_idx ON token_scope(owner_id); +CREATE INDEX at_exp_idx ON access_token(expiration); +CREATE INDEX rf_ahi_idx ON refresh_token(auth_holder_id); +CREATE INDEX rf_tv_idx ON refresh_token(token_value); +CREATE INDEX at_ahi_idx ON access_token(auth_holder_id); +CREATE INDEX aha_oi_idx ON authentication_holder_authority(owner_id); +CREATE INDEX ahe_oi_idx ON authentication_holder_extension(owner_id); +CREATE INDEX ahrp_oi_idx ON authentication_holder_request_parameter(owner_id); +CREATE INDEX ahri_oi_idx ON authentication_holder_resource_id(owner_id); +CREATE INDEX ahrt_oi_idx ON authentication_holder_response_type(owner_id); +CREATE INDEX ahs_oi_idx ON authentication_holder_scope(owner_id); +CREATE INDEX ac_ahi_idx ON authorization_code(auth_holder_id); +CREATE INDEX suaa_oi_idx ON saved_user_auth_authority(owner_id); diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql new file mode 100644 index 0000000000..9f430adace --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/oracle_database_tables.sql @@ -0,0 +1,417 @@ +-- +-- Tables for OIDC Server functionality, Oracle +-- + +CREATE TABLE access_token ( + id NUMBER(19) NOT NULL PRIMARY KEY, + token_value VARCHAR2(4000), + expiration TIMESTAMP, + token_type VARCHAR2(256), + refresh_token_id NUMBER(19), + client_id NUMBER(19), + auth_holder_id NUMBER(19), + approved_site_id NUMBER(19) +); +CREATE SEQUENCE access_token_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE access_token_permissions ( + access_token_id NUMBER(19) NOT NULL, + permission_id NUMBER(19) NOT NULL +); + +CREATE TABLE address ( + id NUMBER(19) NOT NULL PRIMARY KEY, + formatted VARCHAR2(256), + street_address VARCHAR2(256), + locality VARCHAR2(256), + region VARCHAR2(256), + postal_code VARCHAR2(256), + country VARCHAR2(256) +); +CREATE SEQUENCE address_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE approved_site ( + id NUMBER(19) NOT NULL PRIMARY KEY, + user_id VARCHAR2(256), + client_id VARCHAR2(256), + creation_date TIMESTAMP, + access_date TIMESTAMP, + timeout_date TIMESTAMP, + whitelisted_site_id NUMBER(19) +); +CREATE SEQUENCE approved_site_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE approved_site_scope ( + owner_id NUMBER(19), + scope VARCHAR2(256) +); + +CREATE TABLE authentication_holder ( + id NUMBER(19) NOT NULL PRIMARY KEY, + user_auth_id NUMBER(19), + approved NUMBER(1), + redirect_uri VARCHAR2(2048), + client_id VARCHAR2(256), + + CONSTRAINT approved_check CHECK (approved in (1,0)) +); +CREATE SEQUENCE authentication_holder_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE auth_holder_authority ( + owner_id NUMBER(19), + authority VARCHAR2(256) +); + +CREATE TABLE auth_holder_resource_id ( + owner_id NUMBER(19), + resource_id VARCHAR2(2048) +); + +CREATE TABLE auth_holder_response_type ( + owner_id NUMBER(19), + response_type VARCHAR2(2048) +); + +CREATE TABLE auth_holder_extension ( + owner_id NUMBER(19), + extension VARCHAR2(2048), + val VARCHAR2(2048) +); + +CREATE TABLE authentication_holder_scope ( + owner_id NUMBER(19), + scope VARCHAR2(2048) +); + +CREATE TABLE auth_holder_request_parameter ( + owner_id NUMBER(19), + param VARCHAR2(2048), + val VARCHAR2(2048) +); + +CREATE TABLE saved_user_auth ( + id NUMBER(19) NOT NULL PRIMARY KEY, + name VARCHAR2(1024), + authenticated NUMBER(1), + source_class VARCHAR2(2048), + + CONSTRAINT authenticated_check CHECK (authenticated in (1,0)) +); +CREATE SEQUENCE saved_user_auth_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE saved_user_auth_authority ( + owner_id NUMBER(19), + authority VARCHAR2(256) +); + +CREATE TABLE client_authority ( + owner_id NUMBER(19), + authority VARCHAR2(256) +); + +CREATE TABLE authorization_code ( + id NUMBER(19) NOT NULL PRIMARY KEY, + code VARCHAR2(256), + auth_holder_id NUMBER(19), + expiration TIMESTAMP +); +CREATE SEQUENCE authorization_code_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE client_grant_type ( + owner_id NUMBER(19), + grant_type VARCHAR2(2000) +); + +CREATE TABLE client_response_type ( + owner_id NUMBER(19), + response_type VARCHAR2(2000) +); + +CREATE TABLE blacklisted_site ( + id NUMBER(19) NOT NULL PRIMARY KEY, + uri VARCHAR2(2048) +); +CREATE SEQUENCE blacklisted_site_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE client_details ( + id NUMBER(19) NOT NULL PRIMARY KEY, + + client_description VARCHAR2(1024), + reuse_refresh_tokens NUMBER(1) DEFAULT 1 NOT NULL, + dynamically_registered NUMBER(1) DEFAULT 0 NOT NULL, + allow_introspection NUMBER(1) DEFAULT 0 NOT NULL, + id_token_validity_seconds NUMBER(19) DEFAULT 600 NOT NULL, + + client_id VARCHAR2(256), + client_secret VARCHAR2(2048), + access_token_validity_seconds NUMBER(19), + refresh_token_validity_seconds NUMBER(19), + device_code_validity_seconds NUMBER(19), + + application_type VARCHAR2(256), + client_name VARCHAR2(256), + token_endpoint_auth_method VARCHAR2(256), + subject_type VARCHAR2(256), + + logo_uri VARCHAR2(2048), + policy_uri VARCHAR2(2048), + client_uri VARCHAR2(2048), + tos_uri VARCHAR2(2048), + + jwks_uri VARCHAR2(2048), + jwks CLOB, + sector_identifier_uri VARCHAR2(2048), + + request_object_signing_alg VARCHAR2(256), + + user_info_signed_response_alg VARCHAR2(256), + user_info_encrypted_resp_alg VARCHAR2(256), + user_info_encrypted_resp_enc VARCHAR2(256), + + id_token_signed_response_alg VARCHAR2(256), + id_token_encrypted_resp_alg VARCHAR2(256), + id_token_encrypted_resp_enc VARCHAR2(256), + + token_endpoint_auth_sign_alg VARCHAR2(256), + + default_max_age NUMBER(19), + require_auth_time NUMBER(1), + created_at TIMESTAMP, + initiate_login_uri VARCHAR2(2048), + clear_access_tokens_on_refresh NUMBER(1) DEFAULT 1 NOT NULL, + + software_statement VARCHAR(4096), + software_id VARCHAR(2048), + software_statement VARCHAR2(4000), + + code_challenge_method VARCHAR2(256), + + CONSTRAINT client_details_unique UNIQUE (client_id), + CONSTRAINT reuse_refresh_tokens_check CHECK (reuse_refresh_tokens in (1,0)), + CONSTRAINT dynamically_registered_check CHECK (dynamically_registered in (1,0)), + CONSTRAINT allow_introspection_check CHECK (allow_introspection in (1,0)), + CONSTRAINT require_auth_time_check CHECK (require_auth_time in (1,0)), + CONSTRAINT clear_acc_tok_on_refresh_check CHECK (clear_access_tokens_on_refresh in (1,0)) +); +CREATE SEQUENCE client_details_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE client_request_uri ( + owner_id NUMBER(19), + request_uri VARCHAR2(2000) +); + +CREATE TABLE client_post_logout_redir_uri ( + owner_id NUMBER(19), + post_logout_redirect_uri VARCHAR2(2000) +); + +CREATE TABLE client_default_acr_value ( + owner_id NUMBER(19), + default_acr_value VARCHAR2(2000) +); + +CREATE TABLE client_contact ( + owner_id NUMBER(19), + contact VARCHAR2(256) +); + +CREATE TABLE client_redirect_uri ( + owner_id NUMBER(19), + redirect_uri VARCHAR2(2048) +); + +CREATE TABLE client_claims_redirect_uri ( + owner_id NUMBER(19), + redirect_uri VARCHAR2(2048) +); + +CREATE TABLE refresh_token ( + id NUMBER(19) NOT NULL PRIMARY KEY, + token_value VARCHAR2(4000), + expiration TIMESTAMP, + auth_holder_id NUMBER(19), + client_id NUMBER(19) +); +CREATE SEQUENCE refresh_token_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE client_resource ( + owner_id NUMBER(19), + resource_id VARCHAR2(256) +); + +CREATE TABLE client_scope ( + owner_id NUMBER(19), + scope VARCHAR2(2048) +); + +CREATE TABLE token_scope ( + owner_id NUMBER(19), + scope VARCHAR2(2048) +); + +CREATE TABLE system_scope ( + id NUMBER(19) NOT NULL PRIMARY KEY, + scope VARCHAR2(256) NOT NULL, + description VARCHAR2(4000), + icon VARCHAR2(256), + restricted NUMBER(1) DEFAULT 0 NOT NULL, + default_scope NUMBER(1) DEFAULT 0 NOT NULL + + CONSTRAINT system_scope_unique UNIQUE (scope), + CONSTRAINT default_scope_check CHECK (default_scope in (1,0)), + CONSTRAINT restricted_check CHECK (restricted in (1,0)) +); +CREATE SEQUENCE system_scope_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE user_info ( + id NUMBER(19) NOT NULL PRIMARY KEY, + sub VARCHAR2(256), + preferred_username VARCHAR2(256), + name VARCHAR2(256), + given_name VARCHAR2(256), + family_name VARCHAR2(256), + middle_name VARCHAR2(256), + nickname VARCHAR2(256), + profile VARCHAR2(256), + picture VARCHAR2(256), + website VARCHAR2(256), + email VARCHAR2(256), + email_verified NUMBER(1), + gender VARCHAR2(256), + zone_info VARCHAR2(256), + locale VARCHAR2(256), + phone_number VARCHAR2(256), + phone_number_verified NUMBER(1), + address_id VARCHAR2(256), + updated_time VARCHAR2(256), + birthdate VARCHAR2(256), + src VARCHAR2(4000), + + CONSTRAINT email_verified_check CHECK (email_verified in (1,0)), + CONSTRAINT phone_number_verified_check CHECK (phone_number_verified in (1,0)) +); +CREATE SEQUENCE user_info_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE whitelisted_site ( + id NUMBER(19) NOT NULL PRIMARY KEY, + creator_user_id VARCHAR2(256), + client_id VARCHAR2(256) +); +CREATE SEQUENCE whitelisted_site_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE whitelisted_site_scope ( + owner_id NUMBER(19), + scope VARCHAR2(256) +); + +CREATE TABLE pairwise_identifier ( + id NUMBER(19) NOT NULL PRIMARY KEY, + identifier VARCHAR2(256), + sub VARCHAR2(256), + sector_identifier VARCHAR2(2048) +); +CREATE SEQUENCE pairwise_identifier_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE resource_set ( + id NUMBER(19) NOT NULL PRIMARY KEY, + name VARCHAR2(1024) NOT NULL, + uri VARCHAR2(1024), + icon_uri VARCHAR2(1024), + rs_type VARCHAR2(256), + owner VARCHAR2(256) NOT NULL, + client_id VARCHAR2(256) +); +CREATE SEQUENCE resource_set_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE resource_set_scope ( + owner_id NUMBER(19) NOT NULL, + scope VARCHAR2(256) NOT NULL +); + +CREATE TABLE permission_ticket ( + id NUMBER(19) NOT NULL PRIMARY KEY, + ticket VARCHAR2(256) NOT NULL, + permission_id NUMBER(19) NOT NULL, + expiration TIMESTAMP +); +CREATE SEQUENCE permission_ticket_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE permission ( + id NUMBER(19) NOT NULL PRIMARY KEY, + resource_set_id NUMBER(19) +); +CREATE SEQUENCE permission_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE permission_scope ( + owner_id NUMBER(19) NOT NULL, + scope VARCHAR2(256) NOT NULL +); + +CREATE TABLE claim ( + id NUMBER(19) NOT NULL PRIMARY KEY, + name VARCHAR2(256), + friendly_name VARCHAR2(1024), + claim_type VARCHAR2(1024), + claim_value VARCHAR2(1024) +); +CREATE SEQUENCE claim_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE claim_to_policy ( + policy_id NUMBER(19) NOT NULL, + claim_id NUMBER(19) NOT NULL +); + +CREATE TABLE claim_to_permission_ticket ( + permission_ticket_id NUMBER(19) NOT NULL, + claim_id NUMBER(19) NOT NULL +); + +CREATE TABLE policy ( + id NUMBER(19) NOT NULL PRIMARY KEY, + name VARCHAR2(1024), + resource_set_id NUMBER(19) +); +CREATE SEQUENCE policy_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE policy_scope ( + owner_id NUMBER(19) NOT NULL, + scope VARCHAR2(256) NOT NULL +); + +CREATE TABLE claim_token_format ( + owner_id NUMBER(19) NOT NULL, + claim_token_format VARCHAR2(1024) NOT NULL +); + +CREATE TABLE claim_issuer ( + owner_id NUMBER(19) NOT NULL, + issuer VARCHAR2(1024) NOT NULL +); + +CREATE TABLE saved_registered_client ( + id NUMBER(19) NOT NULL PRIMARY KEY, + issuer VARCHAR2(1024), + registered_client CLOB +); +CREATE SEQUENCE saved_registered_client_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; + +CREATE TABLE IF NOT EXISTS device_code ( + id NUMBER(19) NOT NULL PRIMARY KEY, + device_code VARCHAR2(1024), + user_code VARCHAR2(1024), + expiration TIMESTAMP, + client_id VARCHAR2(256), + approved BOOLEAN, + auth_holder_id NUMBER(19) +); + +CREATE TABLE IF NOT EXISTS device_code_scope ( + owner_id NUMBER(19) NOT NULL, + scope VARCHAR2(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS device_code_request_parameter ( + owner_id NUMBER(19), + param VARCHAR2(2048), + val VARCHAR2(2048) +); diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql new file mode 100644 index 0000000000..bb6bc82a23 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql @@ -0,0 +1,26 @@ +-- +-- Insert scope information into the temporary tables. +-- + +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('openid', 'log in using your identity', 'user', 0, 1); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('profile', 'basic profile information', 'list-alt', 0, 1); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('email', 'email address', 'envelope', 0, 1); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('address', 'physical address', 'home', 0, 1); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('phone', 'telephone number', 'bell', 0, 1, 0); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('offline_access', 'offline access', 'time', 0, 0); +-- +-- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store. +-- + +MERGE INTO system_scope + USING (SELECT scope, description, icon, restricted, default_scope FROM system_scope_TEMP) vals + ON (vals.scope = system_scope.scope) + WHEN NOT MATCHED THEN + INSERT (id, scope, description, icon, restricted, default_scope) VALUES(system_scope_seq.nextval, vals.scope, + vals.description, vals.icon, vals.restricted, vals.default_scope); diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql new file mode 100644 index 0000000000..5b67ef668f --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/security-schema_oracle.sql @@ -0,0 +1,18 @@ +-- +-- Tables for Spring Security's user details service +-- + +create table users( + username varchar2(50) not null primary key, + password varchar2(50) not null, + enabled number(1) not null, + + constraint enabled_check check (enabled in (1, 0)) +); + +create table authorities ( + username varchar2(50) not null, + authority varchar2(50) not null, + constraint fk_authorities_users foreign key(username) references users(username), + constraint ix_authority unique (username,authority) +); diff --git a/openid-connect-server-webapp/src/main/resources/db/oracle/users_oracle.sql b/openid-connect-server-webapp/src/main/resources/db/oracle/users_oracle.sql new file mode 100644 index 0000000000..732a13f16e --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/oracle/users_oracle.sql @@ -0,0 +1,39 @@ +-- +-- Insert user information into the temporary tables. To add users to the Oracle database, edit things here. +-- + +INSERT INTO users_TEMP (username, password, enabled) VALUES ('admin','password',1); +INSERT INTO users_TEMP (username, password, enabled) VALUES ('user','password',1); + + +INSERT INTO authorities_TEMP (username, authority) VALUES ('admin','ROLE_ADMIN'); +INSERT INTO authorities_TEMP (username, authority) VALUES('admin','ROLE_USER'); +INSERT INTO authorities_TEMP (username, authority) VALUES('user','ROLE_USER'); + +-- By default, the username column here has to match the username column in the users table, above +INSERT INTO user_info_TEMP (sub, preferred_username, name, email, email_verified) VALUES ('90342.ASDFJWFA','admin','Demo Admin','admin@example.com', 1); +INSERT INTO user_info_TEMP (sub, preferred_username, name, email, email_verified) VALUES ('01921.FLANRJQW','user','Demo User','user@example.com', 1); + + +-- +-- Merge the temporary users safely into the database. This is a two-step process to keep users from being created on every startup with a persistent store. +-- + +MERGE INTO users + USING (SELECT username, password, enabled FROM users_TEMP) vals + ON (vals.username = users.username) + WHEN NOT MATCHED THEN + INSERT (username, password, enabled) VALUES(vals.username, vals.password, vals.enabled); + +MERGE INTO authorities + USING (SELECT username, authority FROM authorities_TEMP) vals + ON (vals.username = authorities.username AND vals.authority = authorities.authority) + WHEN NOT MATCHED THEN + INSERT (username,authority) values (vals.username, vals.authority); + +MERGE INTO user_info + USING (SELECT sub, preferred_username, name, email, email_verified FROM user_info_TEMP) vals + ON (vals.preferred_username = user_info.preferred_username) + WHEN NOT MATCHED THEN + INSERT (id, sub, preferred_username, name, email, email_verified) VALUES (user_info_seq.nextval, vals.sub, vals.preferred_username, vals.name, vals.email, + vals.email_verified); diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/clients.sql b/openid-connect-server-webapp/src/main/resources/db/psql/clients.sql new file mode 100644 index 0000000000..bf14c2b2b6 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/psql/clients.sql @@ -0,0 +1,66 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +--SET AUTOCOMMIT = OFF; + +START TRANSACTION; + +-- +-- Insert client information into the temporary tables. To add clients to the HSQL database, edit things here. +-- + +INSERT INTO client_details_TEMP (client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection) VALUES + ('client', 'secret', 'Test Client', false, null, 3600, 600, true); + +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES + ('client', 'openid'), + ('client', 'profile'), + ('client', 'email'), + ('client', 'address'), + ('client', 'phone'), + ('client', 'offline_access'); + +INSERT INTO client_redirect_uri_TEMP (owner_id, redirect_uri) VALUES + ('client', 'http://localhost/'), + ('client', 'http://localhost:8080/'); + +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES + ('client', 'authorization_code'), + ('client', 'urn:ietf:params:oauth:grant_type:redelegate'), + ('client', 'implicit'), + ('client', 'refresh_token'); + +-- +-- Merge the temporary clients safely into the database. This is a two-step process to keep clients from being created on every startup with a persistent store. +-- + +INSERT INTO client_details (client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection) + SELECT client_id, client_secret, client_name, dynamically_registered, refresh_token_validity_seconds, access_token_validity_seconds, id_token_validity_seconds, allow_introspection FROM client_details_TEMP + ON CONFLICT + DO NOTHING; + +INSERT INTO client_scope (scope) + SELECT scope FROM client_scope_TEMP, client_details WHERE client_details.client_id = client_scope_TEMP.owner_id + ON CONFLICT + DO NOTHING; + +INSERT INTO client_redirect_uri (redirect_uri) + SELECT redirect_uri FROM client_redirect_uri_TEMP, client_details WHERE client_details.client_id = client_redirect_uri_TEMP.owner_id + ON CONFLICT + DO NOTHING; + +INSERT INTO client_grant_type (grant_type) + SELECT grant_type FROM client_grant_type_TEMP, client_details WHERE client_details.client_id = client_grant_type_TEMP.owner_id + ON CONFLICT + DO NOTHING; + +-- +-- Close the transaction and turn autocommit back on +-- + +COMMIT; + +--SET AUTOCOMMIT = ON; + + diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_index.sql b/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_index.sql new file mode 100644 index 0000000000..a641ff8211 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_index.sql @@ -0,0 +1,19 @@ +-- +-- Indexes for PostgreSQL +-- + +CREATE INDEX IF NOT EXISTS at_tv_idx ON access_token(token_value); +CREATE INDEX IF NOT EXISTS ts_oi_idx ON token_scope(owner_id); +CREATE INDEX IF NOT EXISTS at_exp_idx ON access_token(expiration); +CREATE INDEX IF NOT EXISTS rf_ahi_idx ON refresh_token(auth_holder_id); +CREATE INDEX IF NOT EXISTS rf_tv_idx ON refresh_token(token_value); +CREATE INDEX IF NOT EXISTS cd_ci_idx ON client_details(client_id); +CREATE INDEX IF NOT EXISTS at_ahi_idx ON access_token(auth_holder_id); +CREATE INDEX IF NOT EXISTS aha_oi_idx ON authentication_holder_authority(owner_id); +CREATE INDEX IF NOT EXISTS ahe_oi_idx ON authentication_holder_extension(owner_id); +CREATE INDEX IF NOT EXISTS ahrp_oi_idx ON authentication_holder_request_parameter(owner_id); +CREATE INDEX IF NOT EXISTS ahri_oi_idx ON authentication_holder_resource_id(owner_id); +CREATE INDEX IF NOT EXISTS ahrt_oi_idx ON authentication_holder_response_type(owner_id); +CREATE INDEX IF NOT EXISTS ahs_oi_idx ON authentication_holder_scope(owner_id); +CREATE INDEX IF NOT EXISTS ac_ahi_idx ON authorization_code(auth_holder_id); +CREATE INDEX IF NOT EXISTS suaa_oi_idx ON saved_user_auth_authority(owner_id); diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql b/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql new file mode 100644 index 0000000000..be871b7e80 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql @@ -0,0 +1,384 @@ +-- +-- Tables for OIDC Server functionality, PostgreSQL +-- + +CREATE TABLE IF NOT EXISTS access_token ( + id BIGSERIAL PRIMARY KEY, + token_value VARCHAR(4096), + expiration TIMESTAMP, + token_type VARCHAR(256), + refresh_token_id BIGINT, + client_id BIGINT, + auth_holder_id BIGINT, + approved_site_id BIGINT, + UNIQUE(token_value) +); + +CREATE TABLE IF NOT EXISTS access_token_permissions ( + access_token_id BIGINT NOT NULL, + permission_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS address ( + id BIGSERIAL PRIMARY KEY, + formatted VARCHAR(256), + street_address VARCHAR(256), + locality VARCHAR(256), + region VARCHAR(256), + postal_code VARCHAR(256), + country VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS approved_site ( + id BIGSERIAL PRIMARY KEY, + user_id VARCHAR(256), + client_id VARCHAR(256), + creation_date TIMESTAMP, + access_date TIMESTAMP, + timeout_date TIMESTAMP, + whitelisted_site_id BIGINT +); + +CREATE TABLE IF NOT EXISTS approved_site_scope ( + owner_id BIGINT, + scope VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder ( + id BIGSERIAL PRIMARY KEY, + user_auth_id BIGINT, + approved BOOLEAN, + redirect_uri VARCHAR(2048), + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_authority ( + owner_id BIGINT, + authority VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_resource_id ( + owner_id BIGINT, + resource_id VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_response_type ( + owner_id BIGINT, + response_type VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_extension ( + owner_id BIGINT, + extension VARCHAR(2048), + val VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_scope ( + owner_id BIGINT, + scope VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter ( + owner_id BIGINT, + param VARCHAR(2048), + val VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS saved_user_auth ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(1024), + authenticated BOOLEAN, + source_class VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS saved_user_auth_authority ( + owner_id BIGINT, + authority VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS client_authority ( + owner_id BIGINT, + authority VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS authorization_code ( + id BIGSERIAL PRIMARY KEY, + code VARCHAR(256), + auth_holder_id BIGINT, + expiration TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS client_grant_type ( + owner_id BIGINT, + grant_type VARCHAR(2000) +); + +CREATE TABLE IF NOT EXISTS client_response_type ( + owner_id BIGINT, + response_type VARCHAR(2000) +); + +CREATE TABLE IF NOT EXISTS blacklisted_site ( + id BIGSERIAL PRIMARY KEY, + uri VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS client_details ( + id BIGSERIAL PRIMARY KEY, + + client_description VARCHAR(1024), + reuse_refresh_tokens BOOLEAN DEFAULT true NOT NULL, + dynamically_registered BOOLEAN DEFAULT false NOT NULL, + allow_introspection BOOLEAN DEFAULT false NOT NULL, + id_token_validity_seconds BIGINT DEFAULT 600 NOT NULL, + device_code_validity_seconds BIGINT, + + client_id VARCHAR(256), + client_secret VARCHAR(2048), + access_token_validity_seconds BIGINT, + refresh_token_validity_seconds BIGINT, + + application_type VARCHAR(256), + client_name VARCHAR(256), + token_endpoint_auth_method VARCHAR(256), + subject_type VARCHAR(256), + + logo_uri VARCHAR(2048), + policy_uri VARCHAR(2048), + client_uri VARCHAR(2048), + tos_uri VARCHAR(2048), + + jwks_uri VARCHAR(2048), + jwks VARCHAR(8192), + sector_identifier_uri VARCHAR(2048), + + request_object_signing_alg VARCHAR(256), + + user_info_signed_response_alg VARCHAR(256), + user_info_encrypted_response_alg VARCHAR(256), + user_info_encrypted_response_enc VARCHAR(256), + + id_token_signed_response_alg VARCHAR(256), + id_token_encrypted_response_alg VARCHAR(256), + id_token_encrypted_response_enc VARCHAR(256), + + token_endpoint_auth_signing_alg VARCHAR(256), + + default_max_age BIGINT, + require_auth_time BOOLEAN, + created_at TIMESTAMP, + initiate_login_uri VARCHAR(2048), + clear_access_tokens_on_refresh BOOLEAN DEFAULT true NOT NULL, + + software_statement VARCHAR(4096), + software_id VARCHAR(2048), + software_version VARCHAR(2048), + + code_challenge_method VARCHAR(256), + + UNIQUE (client_id) +); + +CREATE TABLE IF NOT EXISTS client_request_uri ( + owner_id BIGINT, + request_uri VARCHAR(2000) +); + +CREATE TABLE IF NOT EXISTS client_post_logout_redirect_uri ( + owner_id BIGINT, + post_logout_redirect_uri VARCHAR(2000) +); + +CREATE TABLE IF NOT EXISTS client_default_acr_value ( + owner_id BIGINT, + default_acr_value VARCHAR(2000) +); + +CREATE TABLE IF NOT EXISTS client_contact ( + owner_id BIGINT, + contact VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS client_redirect_uri ( + owner_id BIGINT, + redirect_uri VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS client_claims_redirect_uri ( + owner_id BIGINT, + redirect_uri VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS refresh_token ( + id BIGSERIAL PRIMARY KEY, + token_value VARCHAR(4096), + expiration TIMESTAMP, + auth_holder_id BIGINT, + client_id BIGINT +); + +CREATE TABLE IF NOT EXISTS client_resource ( + owner_id BIGINT, + resource_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS client_scope ( + owner_id BIGINT, + scope VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS token_scope ( + owner_id BIGINT, + scope VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS system_scope ( + id BIGSERIAL PRIMARY KEY, + scope VARCHAR(256) NOT NULL, + description VARCHAR(4096), + icon VARCHAR(256), + restricted BOOLEAN DEFAULT false NOT NULL, + default_scope BOOLEAN DEFAULT false NOT NULL, + UNIQUE (scope) +); + +CREATE TABLE IF NOT EXISTS user_info ( + id BIGSERIAL PRIMARY KEY, + sub VARCHAR(256), + preferred_username VARCHAR(256), + name VARCHAR(256), + given_name VARCHAR(256), + family_name VARCHAR(256), + middle_name VARCHAR(256), + nickname VARCHAR(256), + profile VARCHAR(256), + picture VARCHAR(256), + website VARCHAR(256), + email VARCHAR(256), + email_verified BOOLEAN, + gender VARCHAR(256), + zone_info VARCHAR(256), + locale VARCHAR(256), + phone_number VARCHAR(256), + phone_number_verified BOOLEAN, + address_id VARCHAR(256), + updated_time VARCHAR(256), + birthdate VARCHAR(256), + src VARCHAR(4096) +); + +CREATE TABLE IF NOT EXISTS whitelisted_site ( + id BIGSERIAL PRIMARY KEY, + creator_user_id VARCHAR(256), + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS whitelisted_site_scope ( + owner_id BIGINT, + scope VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS pairwise_identifier ( + id BIGSERIAL PRIMARY KEY, + identifier VARCHAR(256), + sub VARCHAR(256), + sector_identifier VARCHAR(2048) +); + +CREATE TABLE IF NOT EXISTS resource_set ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(1024) NOT NULL, + uri VARCHAR(1024), + icon_uri VARCHAR(1024), + rs_type VARCHAR(256), + owner VARCHAR(256) NOT NULL, + client_id VARCHAR(256) +); + +CREATE TABLE IF NOT EXISTS resource_set_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS permission_ticket ( + id BIGSERIAL PRIMARY KEY, + ticket VARCHAR(256) NOT NULL, + permission_id BIGINT NOT NULL, + expiration TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS permission ( + id BIGSERIAL PRIMARY KEY, + resource_set_id BIGINT +); + +CREATE TABLE IF NOT EXISTS permission_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(256), + friendly_name VARCHAR(1024), + claim_type VARCHAR(1024), + claim_value VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS claim_to_policy ( + policy_id BIGINT NOT NULL, + claim_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim_to_permission_ticket ( + permission_ticket_id BIGINT NOT NULL, + claim_id BIGINT NOT NULL +); + +CREATE TABLE IF NOT EXISTS policy ( + id BIGSERIAL PRIMARY KEY, + name VARCHAR(1024), + resource_set_id BIGINT +); + +CREATE TABLE IF NOT EXISTS policy_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS claim_token_format ( + owner_id BIGINT NOT NULL, + claim_token_format VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS claim_issuer ( + owner_id BIGINT NOT NULL, + issuer VARCHAR(1024) +); + +CREATE TABLE IF NOT EXISTS saved_registered_client ( + id BIGSERIAL PRIMARY KEY, + issuer VARCHAR(1024), + registered_client VARCHAR(8192) +); + +CREATE TABLE IF NOT EXISTS device_code ( + id BIGSERIAL PRIMARY KEY, + device_code VARCHAR(1024), + user_code VARCHAR(1024), + expiration TIMESTAMP NULL, + client_id VARCHAR(256), + approved BOOLEAN, + auth_holder_id BIGINT +); + +CREATE TABLE IF NOT EXISTS device_code_scope ( + owner_id BIGINT NOT NULL, + scope VARCHAR(256) NOT NULL +); + +CREATE TABLE IF NOT EXISTS device_code_request_parameter ( + owner_id BIGINT, + param VARCHAR(2048), + val VARCHAR(2048) +); diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/scopes.sql b/openid-connect-server-webapp/src/main/resources/db/psql/scopes.sql new file mode 100644 index 0000000000..140c727554 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/psql/scopes.sql @@ -0,0 +1,33 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +--SET AUTOCOMMIT = OFF; + +START TRANSACTION; + +-- +-- Insert scope information into the temporary tables. +-- + +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope) VALUES + ('openid', 'log in using your identity', 'user', false, true), + ('profile', 'basic profile information', 'list-alt', false, true), + ('email', 'email address', 'envelope', false, true), + ('address', 'physical address', 'home', false, true), + ('phone', 'telephone number', 'bell', false, true), + ('offline_access', 'offline access', 'time', false, false); + +-- +-- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store. +-- + +INSERT INTO system_scope (scope, description, icon, restricted, default_scope) + SELECT scope, description, icon, restricted, default_scope FROM system_scope_TEMP + ON CONFLICT(scope) + DO NOTHING; + +COMMIT; + +--SET AUTOCOMMIT = ON; + diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/security-schema.sql b/openid-connect-server-webapp/src/main/resources/db/psql/security-schema.sql new file mode 100644 index 0000000000..bc5d70b880 --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/psql/security-schema.sql @@ -0,0 +1,14 @@ +-- +-- Tables for Spring Security's user details service +-- + +create table IF NOT EXISTS users( + username varchar(50) not null primary key, + password varchar(50) not null, + enabled boolean not null); + + create table IF NOT EXISTS authorities ( + username varchar(50) not null, + authority varchar(50) not null, + constraint fk_authorities_users foreign key(username) references users(username), + constraint ix_authority unique (username,authority)); \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/db/psql/users.sql b/openid-connect-server-webapp/src/main/resources/db/psql/users.sql new file mode 100644 index 0000000000..537330278c --- /dev/null +++ b/openid-connect-server-webapp/src/main/resources/db/psql/users.sql @@ -0,0 +1,55 @@ +-- +-- Turn off autocommit and start a transaction so that we can use the temp tables +-- + +--SET AUTOCOMMIT FALSE; + +START TRANSACTION; + +-- +-- Insert user information into the temporary tables. To add users to the HSQL database, edit things here. +-- + +INSERT INTO users_TEMP (username, password, enabled) VALUES + ('admin','password',true), + ('user','password',true); + + +INSERT INTO authorities_TEMP (username, authority) VALUES + ('admin','ROLE_ADMIN'), + ('admin','ROLE_USER'), + ('user','ROLE_USER'); + +-- By default, the username column here has to match the username column in the users table, above +INSERT INTO user_info_TEMP (sub, preferred_username, name, email, email_verified) VALUES + ('90342.ASDFJWFA','admin','Demo Admin','admin@example.com', true), + ('01921.FLANRJQW','user','Demo User','user@example.com', true); + + +-- +-- Merge the temporary users safely into the database. This is a two-step process to keep users from being created on every startup with a persistent store. +-- + +INSERT INTO users + SELECT username, password, enabled FROM users_TEMP + ON CONFLICT(username) + DO NOTHING; + +INSERT INTO authorities + SELECT username, authority FROM authorities_TEMP + ON CONFLICT(username, authority) + DO NOTHING; + +INSERT INTO user_info (sub, preferred_username, name, email, email_verified) + SELECT sub, preferred_username, name, email, email_verified FROM user_info_TEMP + ON CONFLICT + DO NOTHING; + +-- +-- Close the transaction and turn autocommit back on +-- + +COMMIT; + +--SET AUTOCOMMIT TRUE; + diff --git a/openid-connect-server-webapp/src/main/resources/db/scopes.sql b/openid-connect-server-webapp/src/main/resources/db/scopes.sql deleted file mode 100644 index a7b6f5b1c6..0000000000 --- a/openid-connect-server-webapp/src/main/resources/db/scopes.sql +++ /dev/null @@ -1,33 +0,0 @@ --- --- Turn off autocommit and start a transaction so that we can use the temp tables --- - -SET AUTOCOMMIT FALSE; - -START TRANSACTION; - --- --- Insert scope information into the temporary tables. --- - -INSERT INTO system_scope_TEMP (scope, description, icon, allow_dyn_reg, default_scope, structured, structured_param_description) VALUES - ('openid', 'log in using your identity', 'user', true, true, false, null), - ('profile', 'basic profile information', 'list-alt', true, true, false, null), - ('email', 'email address', 'envelope', true, true, false, null), - ('address', 'physical address', 'home', true, true, false, null), - ('phone', 'telephone number', 'bell', true, true, false, null), - ('offline_access', 'offline access', 'time', true, true, false, null); - --- --- Merge the temporary scopes safely into the database. This is a two-step process to keep scopes from being created on every startup with a persistent store. --- - -MERGE INTO system_scope - USING (SELECT scope, description, icon, allow_dyn_reg, default_scope, structured, structured_param_description FROM system_scope_TEMP) AS vals(scope, description, icon, allow_dyn_reg, default_scope, structured, structured_param_description) - ON vals.scope = system_scope.scope - WHEN NOT MATCHED THEN - INSERT (scope, description, icon, allow_dyn_reg, default_scope, structured, structured_param_description) VALUES(vals.scope, vals.description, vals.icon, vals.allow_dyn_reg, vals.default_scope, vals.structured, vals.structured_param_description); - -COMMIT; - -SET AUTOCOMMIT TRUE; \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/db/upgrade/hsql_1.0-to-1.1_upgrade.sql b/openid-connect-server-webapp/src/main/resources/db/upgrade/hsql_1.0-to-1.1_upgrade.sql deleted file mode 100644 index 79f9e03572..0000000000 --- a/openid-connect-server-webapp/src/main/resources/db/upgrade/hsql_1.0-to-1.1_upgrade.sql +++ /dev/null @@ -1,36 +0,0 @@ --- --- This SQL script will upgrade your MITREid Connect 1.0 database to a MITREid Connect 1.1 database in-place. --- --- NOTICE: Any authorization codes that have not been activated will be removed. --- - -ALTER TABLE access_token ADD COLUMN approved_site_id BIGINT; - -TRUNCATE TABLE authorization_code; - -ALTER TABLE authorization_code DROP COLUMN authorization_request_holder; - -ALTER TABLE authorization_code ADD COLUMN authentication LONGVARBINARY; - -ALTER TABLE client_details ALTER COLUMN id_token_validity_seconds SET NOT NULL; - -ALTER TABLE client_details ALTER COLUMN id_token_validity_seconds SET DEFAULT 600; - -ALTER TABLE client_details ADD COLUMN token_endpoint_auth_signing_alg VARCHAR(256); - -ALTER TABLE system_scope ADD COLUMN structured BOOLEAN NOT NULL DEFAULT false; - -ALTER TABLE system_scope ADD COLUMN structured_param_description VARCHAR(256); - -ALTER TABLE user_info ADD COLUMN phone_number_verified BOOLEAN; - -DROP TABLE client_nonce; - -DROP TABLE event; - -CREATE TABLE IF NOT EXISTS pairwise_identifier ( - id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, - identifier VARCHAR(256), - sub VARCHAR(256), - sector_identifier VARCHAR(2048) -); diff --git a/openid-connect-server-webapp/src/main/resources/db/upgrade/mysql_1.0-to-1.1_upgrade.sql b/openid-connect-server-webapp/src/main/resources/db/upgrade/mysql_1.0-to-1.1_upgrade.sql deleted file mode 100644 index 670dd828fb..0000000000 --- a/openid-connect-server-webapp/src/main/resources/db/upgrade/mysql_1.0-to-1.1_upgrade.sql +++ /dev/null @@ -1,34 +0,0 @@ --- --- This SQL script will upgrade your MITREid Connect 1.0 database to a MITREid Connect 1.1 database in-place. --- --- NOTICE: Any authorization codes that have not been activated will be removed. --- - -ALTER TABLE access_token ADD COLUMN approved_site_id BIGINT; - -TRUNCATE TABLE authorization_code; - -ALTER TABLE authorization_code DROP COLUMN authorization_request_holder; - -ALTER TABLE authorization_code ADD COLUMN authentication LONGBLOB; - -ALTER TABLE client_details MODIFY id_token_validity_seconds BIGINT NOT NULL DEFAULT 600; - -ALTER TABLE client_details ADD COLUMN token_endpoint_auth_signing_alg VARCHAR(256); - -ALTER TABLE system_scope ADD COLUMN structured BOOLEAN NOT NULL DEFAULT 0; - -ALTER TABLE system_scope ADD COLUMN structured_param_description VARCHAR(256); - -ALTER TABLE user_info ADD COLUMN phone_number_verified BOOLEAN; - -DROP TABLE client_nonce; - -DROP TABLE event; - -CREATE TABLE IF NOT EXISTS pairwise_identifier ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - identifier VARCHAR(256), - sub VARCHAR(256), - sector_identifier VARCHAR(2048) -); diff --git a/openid-connect-server-webapp/src/main/resources/keystore.jwks b/openid-connect-server-webapp/src/main/resources/keystore.jwks index d627ecfa32..461413ffe3 100644 --- a/openid-connect-server-webapp/src/main/resources/keystore.jwks +++ b/openid-connect-server-webapp/src/main/resources/keystore.jwks @@ -1,13 +1,12 @@ { - "keys": - [ - { - "alg": "RS256", - "d": "RkRPP2wJQQWOTTjgRnt4WLEl2rpo8Vpg586NhrE8QbJfu0XfrrTL6_pz0kCyqcFcy0Aq0QO5-Rn56M7aZFE5cqo8xq4pPM32IfvlKTuxQxW_0I9fod-dy9GMnhVCO9WKhXQ23H6vKYdq0gsI9ov_S1jIN7JPwdyzzLpAGXf12I0", - "e": "AQAB", - "n": "23zs5r8PQKpsKeoUd2Bjz3TJkUljWqMD8X98SaIb1LE7dCQzi9jwO58FGL0ieY1Dfnr9-g1iiY8sNzV-byawK98W9yFiopaghfoKtxXgUD8pi0fLPeWmAkntjn28Z_WZvvA265ELbBhphPXEJcFhdzUfgESHVuqFMEqp1pB-CP0", - "kty": "RSA", - "kid": "rsa1" - } - ] + "keys": [ + { + "alg": "RS256", + "d": "PvBAngE3kkTnD3yDKo3wCvHJHm20kb9a0FVGLd0s2Y0E_3H2XnZC8-2zPhN6AQTjPhohSDCew20gzm76lyOvMqRiUP2Zpaopa1d2fGvNIQSdM07yKa6EivEYxqPQxa5esoZnexgnb9fom70I8n5OQRNQikwu-az26CsHX2zWMRodzSdN5CXHvb1PV09DmH8azTYwoMElPIqmcTfxiRw2Ov5ucmXXngKRFJgvfUgKd7v4ScBX7sQoQEjWEtt7ta0WvL3Ar5E1RAW4aHxuubZ6AtloxWCf17AAKw03dfP5RDm5TDmgm2B635ecJ7fTvneFmg8W_fdMTPRfBlCGNBp3wQ", + "e": "AQAB", + "n": "qt6yOiI_wCoCVlGO0MySsez0VkSqhPvDl3rfabOslx35mYEO-n4ABfIT5Gn2zN-CeIcOZ5ugAXvIIRWv5H55-tzjFazi5IKkOIMCiz5__MtsdxKCqGlZu2zt-BLpqTOAPiflNPpM3RUAlxKAhnYEqNha6-allPnFQupnW_eTYoyuzuedT7dSp90ry0ZcQDimntXWeaSbrYKCj9Rr9W1jn2uTowUuXaScKXTCjAmJVnsD75JNzQfa8DweklTyWQF-Y5Ky039I0VIu-0CIGhXY48GAFe2EFb8VpNhf07DP63p138RWQ1d3KPEM9mYJVpQC68j3wzDQYSljpLf9by7TGw", + "kty": "RSA", + "kid": "rsa1" + } + ] } \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/resources/log4j.xml b/openid-connect-server-webapp/src/main/resources/log4j.xml index fd2dc2bb29..caed28b323 100644 --- a/openid-connect-server-webapp/src/main/resources/log4j.xml +++ b/openid-connect-server-webapp/src/main/resources/log4j.xml @@ -1,20 +1,21 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> @@ -30,7 +31,28 @@ - + + + + + + + + + + + + + + + + + + + + + + @@ -48,6 +70,10 @@ + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml index ff1eab1eaa..fdbc37ba72 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/application-context.xml @@ -1,20 +1,21 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -51,7 +92,7 @@ - + @@ -61,7 +102,7 @@ - + - - + + + - - - + + + + - - + + + + - + + + + + + + + + + - + + - + + + + + + + + + + + + - - + + - + + - - + + - + + @@ -142,84 +216,67 @@ - - - - - - - - - + + + - + - + - + - - - - - - - - + + + + /introspect + /revoke + /token + + - + - + - + + - + + - + + + + + + - - + + + + + + + + - - - - - - - - - - - - - - - - - - + @@ -253,6 +310,9 @@ + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml new file mode 100644 index 0000000000..59ea49fe90 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/assertion-config.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/authz-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/authz-config.xml new file mode 100644 index 0000000000..0c5e5019f8 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/authz-config.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml index 9ba022718c..36c043a782 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml @@ -1,35 +1,46 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:oauth="http://www.springframework.org/schema/security/oauth2" + xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - + - + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml index a46c3c90c1..0313b5b1b5 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/data-context.xml @@ -1,66 +1,128 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> - + - - + + - + - + + If you are using a file based HSQLDB you should not run this every time. --> - + - + - - - - + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml new file mode 100644 index 0000000000..bcfc14a6c3 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml new file mode 100644 index 0000000000..afe40844af --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/jpa-config.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/local-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/local-config.xml index e8558403b1..e580f6e52a 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/local-config.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/local-config.xml @@ -1,23 +1,34 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:oauth="http://www.springframework.org/schema/security/oauth2" + xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/locale-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/locale-config.xml new file mode 100644 index 0000000000..60cdb6b0f1 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/locale-config.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml index 491bfd93a6..bf9f998652 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/server-config.xml @@ -1,37 +1,77 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + xmlns:context="http://www.springframework.org/schema/context" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:oauth="http://www.springframework.org/schema/security/oauth2" + xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml index 172b63cec1..f37e980ba6 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/spring-servlet.xml @@ -1,23 +1,34 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + xmlns:tx="http://www.springframework.org/schema/tx" + xmlns:context="http://www.springframework.org/schema/context" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:oauth="http://www.springframework.org/schema/security/oauth2" + xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/aboutContent.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/aboutContent.tag deleted file mode 100644 index 56f25d30d0..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/aboutContent.tag +++ /dev/null @@ -1,5 +0,0 @@ -

About

-

This OpenID Connect service is built from the MITREid Connect Open Source project started by The MITRE Corporation.

-

-More information about the project can be found at MITREid Connect on GitHub. There, you can submit bug reports, give feedback, or even contribute code patches for additional features you'd like to see. -

diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag index 43790a6553..d391a30ad0 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/actionmenu.tag @@ -1,18 +1,20 @@ <%@ tag language="java" pageEncoding="UTF-8"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> - -
  • Manage Clients
  • -
  • Whitelisted Clients
  • -
  • Blacklisted Clients
  • -
  • System Scopes
  • + +
  • +
  • +
  • +
  • - -
  • Manage Approved Sites
  • -
  • Manage Active Tokens
  • -
  • View Profile Information
  • + +
  • +
  • +
  • - -
  • Self-service client registration
  • \ No newline at end of file +
  • +
  • +
  • \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/breadcrumbs.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/breadcrumbs.tag deleted file mode 100644 index b131012dab..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/breadcrumbs.tag +++ /dev/null @@ -1,2 +0,0 @@ -<%@attribute name="crumb" required="false" %> - diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/contactContent.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/contactContent.tag deleted file mode 100644 index efb538ff93..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/contactContent.tag +++ /dev/null @@ -1,5 +0,0 @@ -

    Contact

    -

    -To report bugs with the MITREid Connect software itself, use the GitHub issue tracker. -For problems relating to this server, contact the server's administrator. -

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag index f06786c659..4b0aa920ad 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/copyright.tag @@ -1 +1,4 @@ -Powered by MITREid Connect ${project.version} © 2014 The MITRE Corporation and MIT KIT. +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +HEART Mode + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag index b9aa26b42a..2b95de6dcb 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag @@ -1,4 +1,4 @@ -<%@attribute name="js" required="false"%> +<%@ attribute name="js" required="false"%> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> @@ -12,7 +12,7 @@

    - @@ -20,14 +20,26 @@ - + + + - - - - - + + + + +
    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag index b4b20509bb..94f68be330 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/header.tag @@ -1,9 +1,10 @@ <%@attribute name="title" required="false"%> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ tag import="com.google.gson.Gson" %> - + @@ -13,98 +14,41 @@ + - - - + + + + + - + + - + - - - - - + + + + - -
    + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageAbout.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageAbout.tag deleted file mode 100644 index fdc1d4e9fe..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageAbout.tag +++ /dev/null @@ -1,5 +0,0 @@ -

    About

    - -

    This OpenID Connect service is built from the MITREid Connect Open Source project started by The MITRE Corporation.

    - -

    More »

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageContact.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageContact.tag deleted file mode 100644 index 5d46247d2a..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageContact.tag +++ /dev/null @@ -1,5 +0,0 @@ -

    Contact

    - -

    For more information or support, contact the administrators of this system.

    - -

    Email »

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageStats.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageStats.tag deleted file mode 100644 index e7c8cad4d8..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageStats.tag +++ /dev/null @@ -1,8 +0,0 @@ -

    Current Statistics

    - -

    There have been - ${statsSummary["userCount"]} user${statsSummary["userCount"] == 1 ? "" : "s"} - of this system who have logged in to - ${statsSummary["clientCount"]} total site${statsSummary["clientCount"] == 1 ? "" : "s"}, - for a total of - ${statsSummary["approvalCount"]} site approval${statsSummary["approvalCount"] == 1 ? "" : "s"}.

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageWelcome.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageWelcome.tag deleted file mode 100644 index 73a117e33e..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageWelcome.tag +++ /dev/null @@ -1,5 +0,0 @@ -

    Welcome!

    - -

    OpenID Connect is a next-generation protocol built on top of the OAuth2 authorization framework. OpenID Connect lets you log into a remote site using your identity without exposing your credentials, like a username and password.

    - -

    Learn more »

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag new file mode 100644 index 0000000000..78bfe15cb5 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/navmenu.tag @@ -0,0 +1,39 @@ +<%@attribute name="pageName"%> +<%@ tag language="java" pageEncoding="UTF-8"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@ taglib prefix="security" + uri="http://www.springframework.org/security/tags"%> + + + +
  • + + +
  • +
    + + + +
  • +
    + +
  • +
    +
    + + +
  • +
    + +
  • +
    +
    + + +
  • +
    + +
  • +
    +
    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag index 86ab5bc491..93f78c871b 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/sidebar.tag @@ -1,20 +1,18 @@ <%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> -
    - +
    + +
    -
    -
    You are not logged in.
    -
    - -
    +
    + +
    -
    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/statsContent.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/statsContent.tag deleted file mode 100644 index e7c8cad4d8..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/statsContent.tag +++ /dev/null @@ -1,8 +0,0 @@ -

    Current Statistics

    - -

    There have been - ${statsSummary["userCount"]} user${statsSummary["userCount"] == 1 ? "" : "s"} - of this system who have logged in to - ${statsSummary["clientCount"]} total site${statsSummary["clientCount"] == 1 ? "" : "s"}, - for a total of - ${statsSummary["approvalCount"]} site approval${statsSummary["approvalCount"] == 1 ? "" : "s"}.

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag index 85e9e436c3..1bce4a1c5f 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/topbar.tag @@ -1,13 +1,14 @@ <%@attribute name="pageName" required="false"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> - + @@ -17,7 +18,7 @@ - + @@ -33,72 +34,74 @@ - ${config.topbarTitle} - + + + + + + + + + + + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml index adffe8786a..30f3e98a9a 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/task-config.xml @@ -1,24 +1,25 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> @@ -30,6 +31,8 @@ + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/ui-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/ui-config.xml new file mode 100644 index 0000000000..2e0cf646bb --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/ui-config.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + resources/js/client.js + resources/js/grant.js + resources/js/scope.js + resources/js/whitelist.js + resources/js/dynreg.js + resources/js/rsreg.js + resources/js/token.js + resources/js/blacklist.js + resources/js/profile.js + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml index d6ffdc9e9c..1bd665d398 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/user-context.xml @@ -1,66 +1,58 @@ + Copyright 2018 The MIT Internet Trust Consortium + + Portions copyright 2011-2013 The MITRE Corporation + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + xmlns:context="http://www.springframework.org/schema/context" + xmlns:security="http://www.springframework.org/schema/security" + xmlns:oauth="http://www.springframework.org/schema/security/oauth2" + xsi:schemaLocation="http://www.springframework.org/schema/security/oauth2 http://www.springframework.org/schema/security/spring-security-oauth2-2.0.xsd + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> - + + - - - + - - - - - - - - - - - - - + - - + + + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/about.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/about.jsp index 99751cb77e..1bf74f8b5e 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/about.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/about.jsp @@ -1,11 +1,12 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> -<%@ taglib prefix="security" - uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> - - + +
    @@ -14,7 +15,10 @@
    - +

    +

    + +

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp index 060583e204..6526fb8426 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approve.jsp @@ -1,102 +1,197 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ page import="org.springframework.security.core.AuthenticationException"%> <%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%> -<%@ page import="org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter"%> +<%@ page import="org.springframework.security.web.WebAttributes"%> <%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> - - +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + +
    - <% if (session.getAttribute(AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY) != null && !(session.getAttribute(AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY) instanceof UnapprovedClientAuthenticationException)) { %> + <% if (session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null && !(session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) instanceof UnapprovedClientAuthenticationException)) { %>
    × -

    Access could not be granted. - (<%= ((AuthenticationException) session.getAttribute(AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage() %>) +

    + (<%= ((AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)).getMessage() %>)

    <% } %>
    -

    Approve New Site

    +

      + + + + + + + + +

    + action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }authorize" method="post">
    -
    - - <%-- TODO: wire up to stats engine and customize display of this block --%> +
    -
    -

    - Caution: -

    - This client was dynamically registered and has very few other - users on this system. -
    + + + +
    +

    + + + + +

    +
    +
    + + +
    "> +

    + : +

    + +

    + +

    +

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

    +
    +
    +
      -
    • -
      - -
      +
    • +
    + +
    - Do you authorize - " - - - - - - - " - to sign you into their site using your identity? - -
    - more information + +
    + + +
    + +
    +
    +
      + +
    • : ">
    • +
      + +
    • : ">
    • +
      + +
    • : ">
    • +
      + +
    • :
    • +
      +
    +
    +
    -

    -

    -

    - - Redirect URI: - + + +
    +

    + : +

    + + +
    +
    + + + +
    - This client uses a pairwise identifier, which makes it more difficult to correlate your identity between sites. +
    - Access to: + : + + +
    +

    + : +

    +

    + +

    +
    +
    -
    - Remember this decision: + :
    @@ -153,12 +244,26 @@
    - - -   - +

    + + " + + + + + + + "? +

    + + + + + +   +
    @@ -170,6 +275,44 @@ $(document).ready(function() { $('.claim-tooltip').popover(); + $('.claim-tooltip').on('click', function(e) { + e.preventDefault(); + $(this).popover('show'); + }); + + $(document).on('click', '#toggleMoreInformation', function(event) { + event.preventDefault(); + if ($('#moreInformation').is(':visible')) { + // hide it + $('.moreInformationContainer', this.el).removeClass('alert').removeClass('alert-info').addClass('muted'); + $('#moreInformation').hide('fast'); + $('#toggleMoreInformation i').attr('class', 'icon-chevron-right'); + } else { + // show it + $('.moreInformationContainer', this.el).addClass('alert').addClass('alert-info').removeClass('muted'); + $('#moreInformation').show('fast'); + $('#toggleMoreInformation i').attr('class', 'icon-chevron-down'); + } + }); + + var creationDate = ""; + var displayCreationDate = $.t('approve.dynamically-registered-unkown'); + var hoverCreationDate = ""; + if (creationDate != null && moment(creationDate).isValid()) { + creationDate = moment(creationDate); + if (moment().diff(creationDate, 'months') < 6) { + displayCreationDate = creationDate.fromNow(); + } else { + displayCreationDate = "on " + creationDate.format("LL"); + } + hoverCreationDate = creationDate.format("LLL"); + } + + $('#registrationTime').html(displayCreationDate); + $('#registrationTime').attr('title', hoverCreationDate); + + + }); //--> diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp new file mode 100644 index 0000000000..162170311e --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/approveDevice.jsp @@ -0,0 +1,287 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@ page import="org.springframework.security.core.AuthenticationException"%> +<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%> +<%@ page import="org.springframework.security.web.WebAttributes"%> +<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> +<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + +
    + <% if (session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) != null && !(session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION) instanceof UnapprovedClientAuthenticationException)) { %> +
    + × + +

    + (<%= ((AuthenticationException) session.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION)).getMessage() %>) +

    +
    + <% } %> + + +
    +

      + + + + + + + + +

    + +
    + +
    +
    + + + + +
    +

    + + + + +

    +
    +
    + + +
    "> +

    + : +

    + +

    + +

    +

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

    +
    +
    +
    +
    + + +
      +
    • + +
    • +
    + + +
    + +
    + + +
    + +
    +
    +
      + +
    • : ">
    • +
      + +
    • : ">
    • +
      + +
    • : ">
    • +
      + +
    • :
    • +
      +
    +
    +
    +
    +
    + + +
    + +
    +
    + +
    +
    +
    + : + + +
    +

    + : +

    +

    + +

    +
    +
    + +
      + +
    • + + + + + + + + + + + + + + +
    • + : + +
    • +
      +
    +
    + " + > + + + + + + + + + +
    + +
    + +
    +

    + + " + + + + + + + "? +

    + + + + + + +   + +
    + + + +
    +
    + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp index 194ab4782f..cdfcdbcd16 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/contact.jsp @@ -1,11 +1,13 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> -<%@ taglib prefix="security" - uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> - + +
    @@ -13,7 +15,10 @@
    - +

    +

    + +

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp new file mode 100644 index 0000000000..80f601c633 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/deviceApproved.jsp @@ -0,0 +1,39 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@ page import="org.springframework.security.core.AuthenticationException"%> +<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%> +<%@ page import="org.springframework.security.web.WebAttributes"%> +<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> +<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + +
    + +
    +

    + + + + + + + +

    + + + +
    +
    + +
    +
    +
    + +
    +
    + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/error.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/error.jsp new file mode 100644 index 0000000000..66c5f585ed --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/error.jsp @@ -0,0 +1,48 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@page import="org.springframework.http.HttpStatus"%> +<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> +<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> +<%@page import="org.springframework.security.oauth2.common.exceptions.OAuth2Exception"%> +<% + +if (request.getAttribute("error") != null && request.getAttribute("error") instanceof OAuth2Exception) { + request.setAttribute("errorCode", ((OAuth2Exception)request.getAttribute("error")).getOAuth2ErrorCode()); + request.setAttribute("message", ((OAuth2Exception)request.getAttribute("error")).getMessage()); +} else if (request.getAttribute("javax.servlet.error.exception") != null) { + Throwable t = (Throwable)request.getAttribute("javax.servlet.error.exception"); + request.setAttribute("errorCode", t.getClass().getSimpleName() + " (" + request.getAttribute("javax.servlet.error.status_code") + ")"); + request.setAttribute("message", t.getMessage()); +} else if (request.getAttribute("javax.servlet.error.status_code") != null) { + Integer code = (Integer)request.getAttribute("javax.servlet.error.status_code"); + HttpStatus status = HttpStatus.valueOf(code); + request.setAttribute("errorCode", status.toString() + " " + status.getReasonPhrase()); + request.setAttribute("message", request.getAttribute("javax.servlet.error.message")); +} else { + request.setAttribute("errorCode", "Server error"); + request.setAttribute("message", "See the logs for details"); +} + +%> + + + +
    +
    +
    +
    +

    + +

    +

    + +

    +

    + +
    + +
    +
    +
    + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp index 7995554092..5fa2495a53 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/home.jsp @@ -1,23 +1,40 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> - - + + +
    - +
    +
    + +
    +

    +

    +
    +
    - +

    + +

    + +

    »

    - +

    +

    + +

    @@ -25,11 +42,43 @@
    - +

    + +

    + +

    + + + +

    + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/login.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/login.jsp index b600798257..5be8f9b2a5 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/login.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/login.jsp @@ -1,35 +1,51 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -
    +
    -

    Login with Username and Password

    +

    -
    The system was unable to log you in. Please try again.
    +
    -
    -
    -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    + + " autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" value="" id="j_username" name="username"> +
    +
    +
    +
    + + " autocorrect="off" autocapitalize="off" autocomplete="off" spellcheck="false" id="j_password" name="password"> +
    +
    +
    + + " name="submit"> +
    +
    +
    +
    -
    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp new file mode 100644 index 0000000000..a53199e2f4 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp @@ -0,0 +1,55 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@ page import="org.springframework.security.core.AuthenticationException"%> +<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%> +<%@ page import="org.springframework.security.web.WebAttributes"%> +<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> +<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + +
    + +
    + +

    + +
    + +
    +
    + + +
    + + +   + + + + + + + + + +
    +
    + +
    + + +   + +
    +
    + +
    + +
    +
    + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp index 8504925b0a..548819d593 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/manage.jsp @@ -1,22 +1,47 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + + + + - -
    - - - Loading ... -
    -
    -
    -
    + +
    +
    +

    :

    +

    +
    +
    +
    +
    +

    ...

    +
    +
    +
    +
    +
    - + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp new file mode 100644 index 0000000000..03f93974b3 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp @@ -0,0 +1,26 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> +<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> +<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> + + + + +
    + +
    +

    + + +
    +
    + +
    +
    +
    +
    + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp new file mode 100644 index 0000000000..9551acbff3 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp @@ -0,0 +1,61 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> +<%@ page import="org.springframework.security.core.AuthenticationException"%> +<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%> +<%@ page import="org.springframework.security.web.WebAttributes"%> +<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> +<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> +<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> + + + + +
    + +
    + +

    + + + + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    + + +
    + +
    +
    + +
    +
    + +
    +
    + + +
    +
    + +
    + +
    +
    + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/stats.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/stats.jsp index 6ca6011901..8e97e6b368 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/stats.jsp +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/stats.jsp @@ -1,18 +1,26 @@ +<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib prefix="o" tagdir="/WEB-INF/tags"%> -<%@ taglib prefix="security" - uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%> +<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> - + +
    - +

    + +

    + + + +

    diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/web.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/web.xml index 07260431b0..618db1df4a 100644 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/web.xml +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/web.xml @@ -1,23 +1,27 @@ - + + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + metadata-complete="true"> + + @@ -67,5 +71,9 @@ true - + + + /error + + diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/wro.properties b/openid-connect-server-webapp/src/main/webapp/WEB-INF/wro.properties new file mode 100644 index 0000000000..2bc4d6e62a --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/wro.properties @@ -0,0 +1,17 @@ +############################################################################### +# Copyright 2018 The MIT Internet Trust Consortium +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +############################################################################### +preProcessors=cssImport +postProcessors=lessCss diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/wro.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/wro.xml new file mode 100644 index 0000000000..471fd18432 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/wro.xml @@ -0,0 +1,23 @@ + + + + /less/bootstrap.less + + + /less/bootstrap-responsive.less + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/less/accordion.less b/openid-connect-server-webapp/src/main/webapp/less/accordion.less new file mode 100644 index 0000000000..d63523bc8c --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/accordion.less @@ -0,0 +1,34 @@ +// +// Accordion +// -------------------------------------------------- + + +// Parent container +.accordion { + margin-bottom: @baseLineHeight; +} + +// Group == heading + body +.accordion-group { + margin-bottom: 2px; + border: 1px solid #e5e5e5; + .border-radius(@baseBorderRadius); +} +.accordion-heading { + border-bottom: 0; +} +.accordion-heading .accordion-toggle { + display: block; + padding: 8px 15px; +} + +// General toggle styles +.accordion-toggle { + cursor: pointer; +} + +// Inner needs the styles because you can't animate properly with any styles on the element +.accordion-inner { + padding: 9px 15px; + border-top: 1px solid #e5e5e5; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/alerts.less b/openid-connect-server-webapp/src/main/webapp/less/alerts.less new file mode 100644 index 0000000000..0116b191b3 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/alerts.less @@ -0,0 +1,79 @@ +// +// Alerts +// -------------------------------------------------- + + +// Base styles +// ------------------------- + +.alert { + padding: 8px 35px 8px 14px; + margin-bottom: @baseLineHeight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + background-color: @warningBackground; + border: 1px solid @warningBorder; + .border-radius(@baseBorderRadius); +} +.alert, +.alert h4 { + // Specified for the h4 to prevent conflicts of changing @headingsColor + color: @warningText; +} +.alert h4 { + margin: 0; +} + +// Adjust close link position +.alert .close { + position: relative; + top: -2px; + right: -21px; + line-height: @baseLineHeight; +} + + +// Alternate styles +// ------------------------- + +.alert-success { + background-color: @successBackground; + border-color: @successBorder; + color: @successText; +} +.alert-success h4 { + color: @successText; +} +.alert-danger, +.alert-error { + background-color: @errorBackground; + border-color: @errorBorder; + color: @errorText; +} +.alert-danger h4, +.alert-error h4 { + color: @errorText; +} +.alert-info { + background-color: @infoBackground; + border-color: @infoBorder; + color: @infoText; +} +.alert-info h4 { + color: @infoText; +} + + +// Block alerts +// ------------------------- + +.alert-block { + padding-top: 14px; + padding-bottom: 14px; +} +.alert-block > p, +.alert-block > ul { + margin-bottom: 0; +} +.alert-block p + p { + margin-top: 5px; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/bootstrap-responsive.less b/openid-connect-server-webapp/src/main/webapp/less/bootstrap-responsive.less new file mode 100644 index 0000000000..3d4c58cabd --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/bootstrap-responsive.less @@ -0,0 +1,48 @@ +/*! + * Bootstrap Responsive v2.3.2 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + + +// Responsive.less +// For phone and tablet devices +// ------------------------------------------------------------- + + +// REPEAT VARIABLES & MIXINS +// ------------------------- +// Required since we compile the responsive stuff separately + +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + + +// RESPONSIVE CLASSES +// ------------------ + +@import "responsive-utilities.less"; + + +// MEDIA QUERIES +// ------------------ + +// Large desktops +@import "responsive-1200px-min.less"; + +// Tablets to regular desktops +@import "responsive-768px-979px.less"; + +// Phones to portrait tablets and narrow desktops +@import "responsive-767px-max.less"; + + +// RESPONSIVE NAVBAR +// ------------------ + +// From 979px and below, show a button to toggle navbar contents +@import "responsive-navbar.less"; diff --git a/openid-connect-server-webapp/src/main/webapp/less/bootstrap.less b/openid-connect-server-webapp/src/main/webapp/less/bootstrap.less new file mode 100644 index 0000000000..3eabae1440 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/bootstrap.less @@ -0,0 +1,63 @@ +/*! + * Bootstrap v2.3.2 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + */ + +// Core variables and mixins +@import "variables.less"; // Modify this for custom colors, font-sizes, etc +@import "mixins.less"; + +// CSS Reset +@import "reset.less"; + +// Grid system and page structure +@import "scaffolding.less"; +@import "grid.less"; +@import "layouts.less"; + +// Base CSS +@import "type.less"; +@import "code.less"; +@import "forms.less"; +@import "tables.less"; + +// Components: common +@import "sprites.less"; +@import "dropdowns.less"; +@import "wells.less"; +@import "component-animations.less"; +@import "close.less"; + +// Components: Buttons & Alerts +@import "buttons.less"; +@import "button-groups.less"; +@import "alerts.less"; // Note: alerts share common CSS with buttons and thus have styles in buttons.less + +// Components: Nav +@import "navs.less"; +@import "navbar.less"; +@import "breadcrumbs.less"; +@import "pagination.less"; +@import "pager.less"; + +// Components: Popovers +@import "modals.less"; +@import "tooltip.less"; +@import "popovers.less"; + +// Components: Misc +@import "thumbnails.less"; +@import "media.less"; +@import "labels-badges.less"; +@import "progress-bars.less"; +@import "accordion.less"; +@import "carousel.less"; +@import "hero-unit.less"; + +// Utility classes +@import "utilities.less"; // Has to be last to override when necessary diff --git a/openid-connect-server-webapp/src/main/webapp/less/breadcrumbs.less b/openid-connect-server-webapp/src/main/webapp/less/breadcrumbs.less new file mode 100644 index 0000000000..f753df6be8 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/breadcrumbs.less @@ -0,0 +1,24 @@ +// +// Breadcrumbs +// -------------------------------------------------- + + +.breadcrumb { + padding: 8px 15px; + margin: 0 0 @baseLineHeight; + list-style: none; + background-color: #f5f5f5; + .border-radius(@baseBorderRadius); + > li { + display: inline-block; + .ie7-inline-block(); + text-shadow: 0 1px 0 @white; + > .divider { + padding: 0 5px; + color: #ccc; + } + } + > .active { + color: @grayLight; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/button-groups.less b/openid-connect-server-webapp/src/main/webapp/less/button-groups.less new file mode 100644 index 0000000000..55cdc60338 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/button-groups.less @@ -0,0 +1,229 @@ +// +// Button groups +// -------------------------------------------------- + + +// Make the div behave like a button +.btn-group { + position: relative; + display: inline-block; + .ie7-inline-block(); + font-size: 0; // remove as part 1 of font-size inline-block hack + vertical-align: middle; // match .btn alignment given font-size hack above + white-space: nowrap; // prevent buttons from wrapping when in tight spaces (e.g., the table on the tests page) + .ie7-restore-left-whitespace(); +} + +// Space out series of button groups +.btn-group + .btn-group { + margin-left: 5px; +} + +// Optional: Group multiple button groups together for a toolbar +.btn-toolbar { + font-size: 0; // Hack to remove whitespace that results from using inline-block + margin-top: @baseLineHeight / 2; + margin-bottom: @baseLineHeight / 2; + > .btn + .btn, + > .btn-group + .btn, + > .btn + .btn-group { + margin-left: 5px; + } +} + +// Float them, remove border radius, then re-add to first and last elements +.btn-group > .btn { + position: relative; + .border-radius(0); +} +.btn-group > .btn + .btn { + margin-left: -1px; +} +.btn-group > .btn, +.btn-group > .dropdown-menu, +.btn-group > .popover { + font-size: @baseFontSize; // redeclare as part 2 of font-size inline-block hack +} + +// Reset fonts for other sizes +.btn-group > .btn-mini { + font-size: @fontSizeMini; +} +.btn-group > .btn-small { + font-size: @fontSizeSmall; +} +.btn-group > .btn-large { + font-size: @fontSizeLarge; +} + +// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match +.btn-group > .btn:first-child { + margin-left: 0; + .border-top-left-radius(@baseBorderRadius); + .border-bottom-left-radius(@baseBorderRadius); +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.btn-group > .btn:last-child, +.btn-group > .dropdown-toggle { + .border-top-right-radius(@baseBorderRadius); + .border-bottom-right-radius(@baseBorderRadius); +} +// Reset corners for large buttons +.btn-group > .btn.large:first-child { + margin-left: 0; + .border-top-left-radius(@borderRadiusLarge); + .border-bottom-left-radius(@borderRadiusLarge); +} +.btn-group > .btn.large:last-child, +.btn-group > .large.dropdown-toggle { + .border-top-right-radius(@borderRadiusLarge); + .border-bottom-right-radius(@borderRadiusLarge); +} + +// On hover/focus/active, bring the proper btn to front +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active { + z-index: 2; +} + +// On active and open, don't show outline +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} + + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + .box-shadow(~"inset 1px 0 0 rgba(255,255,255,.125), inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + *padding-top: 5px; + *padding-bottom: 5px; +} +.btn-group > .btn-mini + .dropdown-toggle { + padding-left: 5px; + padding-right: 5px; + *padding-top: 2px; + *padding-bottom: 2px; +} +.btn-group > .btn-small + .dropdown-toggle { + *padding-top: 5px; + *padding-bottom: 4px; +} +.btn-group > .btn-large + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; + *padding-top: 7px; + *padding-bottom: 7px; +} + +.btn-group.open { + + // The clickable button for toggling the menu + // Remove the gradient and set the same inset shadow as the :active state + .dropdown-toggle { + background-image: none; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Keep the hover's background when dropdown is open + .btn.dropdown-toggle { + background-color: @btnBackgroundHighlight; + } + .btn-primary.dropdown-toggle { + background-color: @btnPrimaryBackgroundHighlight; + } + .btn-warning.dropdown-toggle { + background-color: @btnWarningBackgroundHighlight; + } + .btn-danger.dropdown-toggle { + background-color: @btnDangerBackgroundHighlight; + } + .btn-success.dropdown-toggle { + background-color: @btnSuccessBackgroundHighlight; + } + .btn-info.dropdown-toggle { + background-color: @btnInfoBackgroundHighlight; + } + .btn-inverse.dropdown-toggle { + background-color: @btnInverseBackgroundHighlight; + } +} + + +// Reposition the caret +.btn .caret { + margin-top: 8px; + margin-left: 0; +} +// Carets in other button sizes +.btn-large .caret { + margin-top: 6px; +} +.btn-large .caret { + border-left-width: 5px; + border-right-width: 5px; + border-top-width: 5px; +} +.btn-mini .caret, +.btn-small .caret { + margin-top: 8px; +} +// Upside down carets for .dropup +.dropup .btn-large .caret { + border-bottom-width: 5px; +} + + + +// Account for other colors +.btn-primary, +.btn-warning, +.btn-danger, +.btn-info, +.btn-success, +.btn-inverse { + .caret { + border-top-color: @white; + border-bottom-color: @white; + } +} + + + +// Vertical button groups +// ---------------------- + +.btn-group-vertical { + display: inline-block; // makes buttons only take up the width they need + .ie7-inline-block(); +} +.btn-group-vertical > .btn { + display: block; + float: none; + max-width: 100%; + .border-radius(0); +} +.btn-group-vertical > .btn + .btn { + margin-left: 0; + margin-top: -1px; +} +.btn-group-vertical > .btn:first-child { + .border-radius(@baseBorderRadius @baseBorderRadius 0 0); +} +.btn-group-vertical > .btn:last-child { + .border-radius(0 0 @baseBorderRadius @baseBorderRadius); +} +.btn-group-vertical > .btn-large:first-child { + .border-radius(@borderRadiusLarge @borderRadiusLarge 0 0); +} +.btn-group-vertical > .btn-large:last-child { + .border-radius(0 0 @borderRadiusLarge @borderRadiusLarge); +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/buttons.less b/openid-connect-server-webapp/src/main/webapp/less/buttons.less new file mode 100644 index 0000000000..4cd4d862b3 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/buttons.less @@ -0,0 +1,228 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +// Core +.btn { + display: inline-block; + .ie7-inline-block(); + padding: 4px 12px; + margin-bottom: 0; // For input.btn + font-size: @baseFontSize; + line-height: @baseLineHeight; + text-align: center; + vertical-align: middle; + cursor: pointer; + .buttonBackground(@btnBackground, @btnBackgroundHighlight, @grayDark, 0 1px 1px rgba(255,255,255,.75)); + border: 1px solid @btnBorder; + *border: 0; // Remove the border to prevent IE7's black border on input:focus + border-bottom-color: darken(@btnBorder, 10%); + .border-radius(@baseBorderRadius); + .ie7-restore-left-whitespace(); // Give IE7 some love + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)"); + + // Hover/focus state + &:hover, + &:focus { + color: @grayDark; + text-decoration: none; + background-position: 0 -15px; + + // transition is only when going to hover/focus, otherwise the background + // behind the gradient (there for IE<=9 fallback) gets mismatched + .transition(background-position .1s linear); + } + + // Focus state for keyboard and accessibility + &:focus { + .tab-focus(); + } + + // Active state + &.active, + &:active { + background-image: none; + outline: 0; + .box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)"); + } + + // Disabled state + &.disabled, + &[disabled] { + cursor: default; + background-image: none; + .opacity(65); + .box-shadow(none); + } + +} + + + +// Button Sizes +// -------------------------------------------------- + +// Large +.btn-large { + padding: @paddingLarge; + font-size: @fontSizeLarge; + .border-radius(@borderRadiusLarge); +} +.btn-large [class^="icon-"], +.btn-large [class*=" icon-"] { + margin-top: 4px; +} + +// Small +.btn-small { + padding: @paddingSmall; + font-size: @fontSizeSmall; + .border-radius(@borderRadiusSmall); +} +.btn-small [class^="icon-"], +.btn-small [class*=" icon-"] { + margin-top: 0; +} +.btn-mini [class^="icon-"], +.btn-mini [class*=" icon-"] { + margin-top: -1px; +} + +// Mini +.btn-mini { + padding: @paddingMini; + font-size: @fontSizeMini; + .border-radius(@borderRadiusSmall); +} + + +// Block button +// ------------------------- + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; + .box-sizing(border-box); +} + +// Vertically space out multiple block buttons +.btn-block + .btn-block { + margin-top: 5px; +} + +// Specificity overrides +input[type="submit"], +input[type="reset"], +input[type="button"] { + &.btn-block { + width: 100%; + } +} + + + +// Alternate buttons +// -------------------------------------------------- + +// Provide *some* extra contrast for those who can get it +.btn-primary.active, +.btn-warning.active, +.btn-danger.active, +.btn-success.active, +.btn-info.active, +.btn-inverse.active { + color: rgba(255,255,255,.75); +} + +// Set the backgrounds +// ------------------------- +.btn-primary { + .buttonBackground(@btnPrimaryBackground, @btnPrimaryBackgroundHighlight); +} +// Warning appears are orange +.btn-warning { + .buttonBackground(@btnWarningBackground, @btnWarningBackgroundHighlight); +} +// Danger and error appear as red +.btn-danger { + .buttonBackground(@btnDangerBackground, @btnDangerBackgroundHighlight); +} +// Success appears as green +.btn-success { + .buttonBackground(@btnSuccessBackground, @btnSuccessBackgroundHighlight); +} +// Info appears as a neutral blue +.btn-info { + .buttonBackground(@btnInfoBackground, @btnInfoBackgroundHighlight); +} +// Inverse appears as dark gray +.btn-inverse { + .buttonBackground(@btnInverseBackground, @btnInverseBackgroundHighlight); +} + + +// Cross-browser Jank +// -------------------------------------------------- + +button.btn, +input[type="submit"].btn { + + // Firefox 3.6 only I believe + &::-moz-focus-inner { + padding: 0; + border: 0; + } + + // IE7 has some default padding on button controls + *padding-top: 3px; + *padding-bottom: 3px; + + &.btn-large { + *padding-top: 7px; + *padding-bottom: 7px; + } + &.btn-small { + *padding-top: 3px; + *padding-bottom: 3px; + } + &.btn-mini { + *padding-top: 1px; + *padding-bottom: 1px; + } +} + + +// Link buttons +// -------------------------------------------------- + +// Make a button look and behave like a link +.btn-link, +.btn-link:active, +.btn-link[disabled] { + background-color: transparent; + background-image: none; + .box-shadow(none); +} +.btn-link { + border-color: transparent; + cursor: pointer; + color: @linkColor; + .border-radius(0); +} +.btn-link:hover, +.btn-link:focus { + color: @linkColorHover; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +.btn-link[disabled]:focus { + color: @grayDark; + text-decoration: none; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/carousel.less b/openid-connect-server-webapp/src/main/webapp/less/carousel.less new file mode 100644 index 0000000000..55bc050144 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/carousel.less @@ -0,0 +1,158 @@ +// +// Carousel +// -------------------------------------------------- + + +.carousel { + position: relative; + margin-bottom: @baseLineHeight; + line-height: 1; +} + +.carousel-inner { + overflow: hidden; + width: 100%; + position: relative; +} + +.carousel-inner { + + > .item { + display: none; + position: relative; + .transition(.6s ease-in-out left); + + // Account for jankitude on images + > img, + > a > img { + display: block; + line-height: 1; + } + } + + > .active, + > .next, + > .prev { display: block; } + + > .active { + left: 0; + } + + > .next, + > .prev { + position: absolute; + top: 0; + width: 100%; + } + + > .next { + left: 100%; + } + > .prev { + left: -100%; + } + > .next.left, + > .prev.right { + left: 0; + } + + > .active.left { + left: -100%; + } + > .active.right { + left: 100%; + } + +} + +// Left/right controls for nav +// --------------------------- + +.carousel-control { + position: absolute; + top: 40%; + left: 15px; + width: 40px; + height: 40px; + margin-top: -20px; + font-size: 60px; + font-weight: 100; + line-height: 30px; + color: @white; + text-align: center; + background: @grayDarker; + border: 3px solid @white; + .border-radius(23px); + .opacity(50); + + // we can't have this transition here + // because webkit cancels the carousel + // animation if you trip this while + // in the middle of another animation + // ;_; + // .transition(opacity .2s linear); + + // Reposition the right one + &.right { + left: auto; + right: 15px; + } + + // Hover/focus state + &:hover, + &:focus { + color: @white; + text-decoration: none; + .opacity(90); + } +} + +// Carousel indicator pips +// ----------------------------- +.carousel-indicators { + position: absolute; + top: 15px; + right: 15px; + z-index: 5; + margin: 0; + list-style: none; + + li { + display: block; + float: left; + width: 10px; + height: 10px; + margin-left: 5px; + text-indent: -999px; + background-color: #ccc; + background-color: rgba(255,255,255,.25); + border-radius: 5px; + } + .active { + background-color: #fff; + } +} + +// Caption for text below images +// ----------------------------- + +.carousel-caption { + position: absolute; + left: 0; + right: 0; + bottom: 0; + padding: 15px; + background: @grayDark; + background: rgba(0,0,0,.75); +} +.carousel-caption h4, +.carousel-caption p { + color: @white; + line-height: @baseLineHeight; +} +.carousel-caption h4 { + margin: 0 0 5px; +} +.carousel-caption p { + margin-bottom: 0; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/close.less b/openid-connect-server-webapp/src/main/webapp/less/close.less new file mode 100644 index 0000000000..4c626bda6c --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/close.less @@ -0,0 +1,32 @@ +// +// Close icons +// -------------------------------------------------- + + +.close { + float: right; + font-size: 20px; + font-weight: bold; + line-height: @baseLineHeight; + color: @black; + text-shadow: 0 1px 0 rgba(255,255,255,1); + .opacity(20); + &:hover, + &:focus { + color: @black; + text-decoration: none; + cursor: pointer; + .opacity(40); + } +} + +// Additional properties for button version +// iOS requires the button element instead of an anchor tag. +// If you want the anchor version, it requires `href="#"`. +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; +} \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/less/code.less b/openid-connect-server-webapp/src/main/webapp/less/code.less new file mode 100644 index 0000000000..266a926e73 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/code.less @@ -0,0 +1,61 @@ +// +// Code (inline and blocK) +// -------------------------------------------------- + + +// Inline and block code styles +code, +pre { + padding: 0 3px 2px; + #font > #family > .monospace; + font-size: @baseFontSize - 2; + color: @grayDark; + .border-radius(3px); +} + +// Inline code +code { + padding: 2px 4px; + color: #d14; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + white-space: nowrap; +} + +// Blocks of code +pre { + display: block; + padding: (@baseLineHeight - 1) / 2; + margin: 0 0 @baseLineHeight / 2; + font-size: @baseFontSize - 1; // 14px to 13px + line-height: @baseLineHeight; + word-break: break-all; + word-wrap: break-word; + white-space: pre; + white-space: pre-wrap; + background-color: #f5f5f5; + border: 1px solid #ccc; // fallback for IE7-8 + border: 1px solid rgba(0,0,0,.15); + .border-radius(@baseBorderRadius); + + // Make prettyprint styles more spaced out for readability + &.prettyprint { + margin-bottom: @baseLineHeight; + } + + // Account for some code outputs that place code tags in pre tags + code { + padding: 0; + color: inherit; + white-space: pre; + white-space: pre-wrap; + background-color: transparent; + border: 0; + } +} + +// Enable scrollable blocks of code +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/less/component-animations.less b/openid-connect-server-webapp/src/main/webapp/less/component-animations.less new file mode 100644 index 0000000000..d614263a76 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/component-animations.less @@ -0,0 +1,22 @@ +// +// Component animations +// -------------------------------------------------- + + +.fade { + opacity: 0; + .transition(opacity .15s linear); + &.in { + opacity: 1; + } +} + +.collapse { + position: relative; + height: 0; + overflow: hidden; + .transition(height .35s ease); + &.in { + height: auto; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/dropdowns.less b/openid-connect-server-webapp/src/main/webapp/less/dropdowns.less new file mode 100644 index 0000000000..9e47b47151 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/dropdowns.less @@ -0,0 +1,248 @@ +// +// Dropdown menus +// -------------------------------------------------- + + +// Use the .menu class on any
  • element within the topbar or ul.tabs and you'll get some superfancy dropdowns +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle { + // The caret makes the toggle a bit too tall in IE7 + *margin-bottom: -3px; +} +.dropdown-toggle:active, +.open .dropdown-toggle { + outline: 0; +} + +// Dropdown arrow/caret +// -------------------- +.caret { + display: inline-block; + width: 0; + height: 0; + vertical-align: top; + border-top: 4px solid @black; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + content: ""; +} + +// Place the caret +.dropdown .caret { + margin-top: 8px; + margin-left: 2px; +} + +// The dropdown menu (ul) +// ---------------------- +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: @zindexDropdown; + display: none; // none by default, but block on "open" of the menu + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; // override default ul + list-style: none; + background-color: @dropdownBackground; + border: 1px solid #ccc; // Fallback for IE7-8 + border: 1px solid @dropdownBorder; + *border-right-width: 2px; + *border-bottom-width: 2px; + .border-radius(6px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + + // Aligns the dropdown menu to right + &.pull-right { + right: 0; + left: auto; + } + + // Dividers (basically an hr) within the dropdown + .divider { + .nav-divider(@dropdownDividerTop, @dropdownDividerBottom); + } + + // Links within the dropdown menu + > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: @baseLineHeight; + color: @dropdownLinkColor; + white-space: nowrap; + } +} + +// Hover/Focus state +// ----------- +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus, +.dropdown-submenu:hover > a, +.dropdown-submenu:focus > a { + text-decoration: none; + color: @dropdownLinkColorHover; + #gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%)); +} + +// Active state +// ------------ +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: @dropdownLinkColorActive; + text-decoration: none; + outline: 0; + #gradient > .vertical(@dropdownLinkBackgroundActive, darken(@dropdownLinkBackgroundActive, 5%)); +} + +// Disabled state +// -------------- +// Gray out text and ensure the hover/focus state remains gray +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: @grayLight; +} +// Nuke hover/focus effects +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; // Remove CSS gradient + .reset-filter(); + cursor: default; +} + +// Open state for the dropdown +// --------------------------- +.open { + // IE7's z-index only goes to the nearest positioned ancestor, which would + // make the menu appear below buttons that appeared later on the page + *z-index: @zindexDropdown; + + & > .dropdown-menu { + display: block; + } +} + +// Backdrop to catch body clicks on mobile, etc. +// --------------------------- +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: @zindexDropdown - 10; +} + +// Right aligned dropdowns +// --------------------------- +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} + +// Allow for dropdowns to go bottom up (aka, dropup-menu) +// ------------------------------------------------------ +// Just add .dropup after the standard .dropdown class and you're set, bro. +// TODO: abstract this so that the navbar fixed styles are not placed here? +.dropup, +.navbar-fixed-bottom .dropdown { + // Reverse the caret + .caret { + border-top: 0; + border-bottom: 4px solid @black; + content: ""; + } + // Different positioning for bottom up menu + .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; + } +} + +// Sub menus +// --------------------------- +.dropdown-submenu { + position: relative; +} +// Default dropdowns +.dropdown-submenu > .dropdown-menu { + top: 0; + left: 100%; + margin-top: -6px; + margin-left: -1px; + .border-radius(0 6px 6px 6px); +} +.dropdown-submenu:hover > .dropdown-menu { + display: block; +} + +// Dropups +.dropup .dropdown-submenu > .dropdown-menu { + top: auto; + bottom: 0; + margin-top: 0; + margin-bottom: -2px; + .border-radius(5px 5px 5px 0); +} + +// Caret to indicate there is a submenu +.dropdown-submenu > a:after { + display: block; + content: " "; + float: right; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + border-width: 5px 0 5px 5px; + border-left-color: darken(@dropdownBackground, 20%); + margin-top: 5px; + margin-right: -10px; +} +.dropdown-submenu:hover > a:after { + border-left-color: @dropdownLinkColorHover; +} + +// Left aligned submenus +.dropdown-submenu.pull-left { + // Undo the float + // Yes, this is awkward since .pull-left adds a float, but it sticks to our conventions elsewhere. + float: none; + + // Positioning the submenu + > .dropdown-menu { + left: -100%; + margin-left: 10px; + .border-radius(6px 0 6px 6px); + } +} + +// Tweak nav headers +// ----------------- +// Increase padding from 15px to 20px on sides +.dropdown .dropdown-menu .nav-header { + padding-left: 20px; + padding-right: 20px; +} + +// Typeahead +// --------- +.typeahead { + z-index: 1051; + margin-top: 2px; // give it some space to breathe + .border-radius(@baseBorderRadius); +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/forms.less b/openid-connect-server-webapp/src/main/webapp/less/forms.less new file mode 100644 index 0000000000..06767bdd3e --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/forms.less @@ -0,0 +1,690 @@ +// +// Forms +// -------------------------------------------------- + + +// GENERAL STYLES +// -------------- + +// Make all forms have space below them +form { + margin: 0 0 @baseLineHeight; +} + +fieldset { + padding: 0; + margin: 0; + border: 0; +} + +// Groups of fields with labels on top (legends) +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + line-height: @baseLineHeight * 2; + color: @grayDark; + border: 0; + border-bottom: 1px solid #e5e5e5; + + // Small + small { + font-size: @baseLineHeight * .75; + color: @grayLight; + } +} + +// Set font for forms +label, +input, +button, +select, +textarea { + #font > .shorthand(@baseFontSize,normal,@baseLineHeight); // Set size, weight, line-height here +} +input, +button, +select, +textarea { + font-family: @baseFontFamily; // And only set font-family here for those that need it (note the missing label element) +} + +// Identify controls by their labels +label { + display: block; + margin-bottom: 5px; +} + +// Form controls +// ------------------------- + +// Shared size and type resets +select, +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + display: inline-block; + height: @baseLineHeight; + padding: 4px 6px; + margin-bottom: @baseLineHeight / 2; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @gray; + .border-radius(@inputBorderRadius); + vertical-align: middle; +} + +// Reset appearance properties for textual inputs and textarea +// Declare width for legacy (can't be on input[type=*] selectors or it's too specific) +input, +textarea, +.uneditable-input { + width: 206px; // plus 12px padding and 2px border +} +// Reset height since textareas have rows +textarea { + height: auto; +} +// Everything else +textarea, +input[type="text"], +input[type="password"], +input[type="datetime"], +input[type="datetime-local"], +input[type="date"], +input[type="month"], +input[type="time"], +input[type="week"], +input[type="number"], +input[type="email"], +input[type="url"], +input[type="search"], +input[type="tel"], +input[type="color"], +.uneditable-input { + background-color: @inputBackground; + border: 1px solid @inputBorder; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); + .transition(~"border linear .2s, box-shadow linear .2s"); + + // Focus state + &:focus { + border-color: rgba(82,168,236,.8); + outline: 0; + outline: thin dotted \9; /* IE6-9 */ + .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)"); + } +} + +// Position radios and checkboxes better +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + *margin-top: 0; /* IE7 */ + margin-top: 1px \9; /* IE8-9 */ + line-height: normal; +} + +// Reset width of input images, buttons, radios, checkboxes +input[type="file"], +input[type="image"], +input[type="submit"], +input[type="reset"], +input[type="button"], +input[type="radio"], +input[type="checkbox"] { + width: auto; // Override of generic input selector +} + +// Set the height of select and file controls to match text inputs +select, +input[type="file"] { + height: @inputHeight; /* In IE7, the height of the select element cannot be changed by height, only font-size */ + *margin-top: 4px; /* For IE7, add top margin to align select with labels */ + line-height: @inputHeight; +} + +// Make select elements obey height by applying a border +select { + width: 220px; // default input width + 10px of padding that doesn't get applied + border: 1px solid @inputBorder; + background-color: @inputBackground; // Chrome on Linux and Mobile Safari need background-color +} + +// Make multiple select elements height not fixed +select[multiple], +select[size] { + height: auto; +} + +// Focus for select, file, radio, and checkbox +select:focus, +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + .tab-focus(); +} + + +// Uneditable inputs +// ------------------------- + +// Make uneditable inputs look inactive +.uneditable-input, +.uneditable-textarea { + color: @grayLight; + background-color: darken(@inputBackground, 1%); + border-color: @inputBorder; + .box-shadow(inset 0 1px 2px rgba(0,0,0,.025)); + cursor: not-allowed; +} + +// For text that needs to appear as an input but should not be an input +.uneditable-input { + overflow: hidden; // prevent text from wrapping, but still cut it off like an input does + white-space: nowrap; +} + +// Make uneditable textareas behave like a textarea +.uneditable-textarea { + width: auto; + height: auto; +} + + +// Placeholder +// ------------------------- + +// Placeholder text gets special styles because when browsers invalidate entire lines if it doesn't understand a selector +input, +textarea { + .placeholder(); +} + + +// CHECKBOXES & RADIOS +// ------------------- + +// Indent the labels to position radios/checkboxes as hanging +.radio, +.checkbox { + min-height: @baseLineHeight; // clear the floating input if there is no label text + padding-left: 20px; +} +.radio input[type="radio"], +.checkbox input[type="checkbox"] { + float: left; + margin-left: -20px; +} + +// Move the options list down to align with labels +.controls > .radio:first-child, +.controls > .checkbox:first-child { + padding-top: 5px; // has to be padding because margin collaspes +} + +// Radios and checkboxes on same line +// TODO v3: Convert .inline to .control-inline +.radio.inline, +.checkbox.inline { + display: inline-block; + padding-top: 5px; + margin-bottom: 0; + vertical-align: middle; +} +.radio.inline + .radio.inline, +.checkbox.inline + .checkbox.inline { + margin-left: 10px; // space out consecutive inline controls +} + + + +// INPUT SIZES +// ----------- + +// General classes for quick sizes +.input-mini { width: 60px; } +.input-small { width: 90px; } +.input-medium { width: 150px; } +.input-large { width: 210px; } +.input-xlarge { width: 270px; } +.input-xxlarge { width: 530px; } + +// Grid style input sizes +input[class*="span"], +select[class*="span"], +textarea[class*="span"], +.uneditable-input[class*="span"], +// Redeclare since the fluid row class is more specific +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"] { + float: none; + margin-left: 0; +} +// Ensure input-prepend/append never wraps +.input-append input[class*="span"], +.input-append .uneditable-input[class*="span"], +.input-prepend input[class*="span"], +.input-prepend .uneditable-input[class*="span"], +.row-fluid input[class*="span"], +.row-fluid select[class*="span"], +.row-fluid textarea[class*="span"], +.row-fluid .uneditable-input[class*="span"], +.row-fluid .input-prepend [class*="span"], +.row-fluid .input-append [class*="span"] { + display: inline-block; +} + + + +// GRID SIZING FOR INPUTS +// ---------------------- + +// Grid sizes +#grid > .input(@gridColumnWidth, @gridGutterWidth); + +// Control row for multiple inputs per line +.controls-row { + .clearfix(); // Clear the float from controls +} + +// Float to collapse white-space for proper grid alignment +.controls-row [class*="span"], +// Redeclare the fluid grid collapse since we undo the float for inputs +.row-fluid .controls-row [class*="span"] { + float: left; +} +// Explicity set top padding on all checkboxes/radios, not just first-child +.controls-row .checkbox[class*="span"], +.controls-row .radio[class*="span"] { + padding-top: 5px; +} + + + + +// DISABLED STATE +// -------------- + +// Disabled and read-only inputs +input[disabled], +select[disabled], +textarea[disabled], +input[readonly], +select[readonly], +textarea[readonly] { + cursor: not-allowed; + background-color: @inputDisabledBackground; +} +// Explicitly reset the colors here +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"][readonly], +input[type="checkbox"][readonly] { + background-color: transparent; +} + + + + +// FORM FIELD FEEDBACK STATES +// -------------------------- + +// Warning +.control-group.warning { + .formFieldState(@warningText, @warningText, @warningBackground); +} +// Error +.control-group.error { + .formFieldState(@errorText, @errorText, @errorBackground); +} +// Success +.control-group.success { + .formFieldState(@successText, @successText, @successBackground); +} +// Success +.control-group.info { + .formFieldState(@infoText, @infoText, @infoBackground); +} + +// HTML5 invalid states +// Shares styles with the .control-group.error above +input:focus:invalid, +textarea:focus:invalid, +select:focus:invalid { + color: #b94a48; + border-color: #ee5f5b; + &:focus { + border-color: darken(#ee5f5b, 10%); + @shadow: 0 0 6px lighten(#ee5f5b, 20%); + .box-shadow(@shadow); + } +} + + + +// FORM ACTIONS +// ------------ + +.form-actions { + padding: (@baseLineHeight - 1) 20px @baseLineHeight; + margin-top: @baseLineHeight; + margin-bottom: @baseLineHeight; + background-color: @formActionsBackground; + border-top: 1px solid #e5e5e5; + .clearfix(); // Adding clearfix to allow for .pull-right button containers +} + + + +// HELP TEXT +// --------- + +.help-block, +.help-inline { + color: lighten(@textColor, 15%); // lighten the text some for contrast +} + +.help-block { + display: block; // account for any element using help-block + margin-bottom: @baseLineHeight / 2; +} + +.help-inline { + display: inline-block; + .ie7-inline-block(); + vertical-align: middle; + padding-left: 5px; +} + + + +// INPUT GROUPS +// ------------ + +// Allow us to put symbols and text within the input field for a cleaner look +.input-append, +.input-prepend { + display: inline-block; + margin-bottom: @baseLineHeight / 2; + vertical-align: middle; + font-size: 0; // white space collapse hack + white-space: nowrap; // Prevent span and input from separating + + // Reset the white space collapse hack + input, + select, + .uneditable-input, + .dropdown-menu, + .popover { + font-size: @baseFontSize; + } + + input, + select, + .uneditable-input { + position: relative; // placed here by default so that on :focus we can place the input above the .add-on for full border and box-shadow goodness + margin-bottom: 0; // prevent bottom margin from screwing up alignment in stacked forms + *margin-left: 0; + vertical-align: top; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + // Make input on top when focused so blue border and shadow always show + &:focus { + z-index: 2; + } + } + .add-on { + display: inline-block; + width: auto; + height: @baseLineHeight; + min-width: 16px; + padding: 4px 5px; + font-size: @baseFontSize; + font-weight: normal; + line-height: @baseLineHeight; + text-align: center; + text-shadow: 0 1px 0 @white; + background-color: @grayLighter; + border: 1px solid #ccc; + } + .add-on, + .btn, + .btn-group > .dropdown-toggle { + vertical-align: top; + .border-radius(0); + } + .active { + background-color: lighten(@green, 30); + border-color: @green; + } +} + +.input-prepend { + .add-on, + .btn { + margin-right: -1px; + } + .add-on:first-child, + .btn:first-child { + // FYI, `.btn:first-child` accounts for a button group that's prepended + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } +} + +.input-append { + input, + select, + .uneditable-input { + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + + .btn-group .btn:last-child { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on, + .btn, + .btn-group { + margin-left: -1px; + } + .add-on:last-child, + .btn:last-child, + .btn-group:last-child > .dropdown-toggle { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } +} + +// Remove all border-radius for inputs with both prepend and append +.input-prepend.input-append { + input, + select, + .uneditable-input { + .border-radius(0); + + .btn-group .btn { + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + } + .add-on:first-child, + .btn:first-child { + margin-right: -1px; + .border-radius(@inputBorderRadius 0 0 @inputBorderRadius); + } + .add-on:last-child, + .btn:last-child { + margin-left: -1px; + .border-radius(0 @inputBorderRadius @inputBorderRadius 0); + } + .btn-group:first-child { + margin-left: 0; + } +} + + + + +// SEARCH FORM +// ----------- + +input.search-query { + padding-right: 14px; + padding-right: 4px \9; + padding-left: 14px; + padding-left: 4px \9; /* IE7-8 doesn't have border-radius, so don't indent the padding */ + margin-bottom: 0; // Remove the default margin on all inputs + .border-radius(15px); +} + +/* Allow for input prepend/append in search forms */ +.form-search .input-append .search-query, +.form-search .input-prepend .search-query { + .border-radius(0); // Override due to specificity +} +.form-search .input-append .search-query { + .border-radius(14px 0 0 14px); +} +.form-search .input-append .btn { + .border-radius(0 14px 14px 0); +} +.form-search .input-prepend .search-query { + .border-radius(0 14px 14px 0); +} +.form-search .input-prepend .btn { + .border-radius(14px 0 0 14px); +} + + + + +// HORIZONTAL & VERTICAL FORMS +// --------------------------- + +// Common properties +// ----------------- + +.form-search, +.form-inline, +.form-horizontal { + input, + textarea, + select, + .help-inline, + .uneditable-input, + .input-prepend, + .input-append { + display: inline-block; + .ie7-inline-block(); + margin-bottom: 0; + vertical-align: middle; + } + // Re-hide hidden elements due to specifity + .hide { + display: none; + } +} +.form-search label, +.form-inline label, +.form-search .btn-group, +.form-inline .btn-group { + display: inline-block; +} +// Remove margin for input-prepend/-append +.form-search .input-append, +.form-inline .input-append, +.form-search .input-prepend, +.form-inline .input-prepend { + margin-bottom: 0; +} +// Inline checkbox/radio labels (remove padding on left) +.form-search .radio, +.form-search .checkbox, +.form-inline .radio, +.form-inline .checkbox { + padding-left: 0; + margin-bottom: 0; + vertical-align: middle; +} +// Remove float and margin, set to inline-block +.form-search .radio input[type="radio"], +.form-search .checkbox input[type="checkbox"], +.form-inline .radio input[type="radio"], +.form-inline .checkbox input[type="checkbox"] { + float: left; + margin-right: 3px; + margin-left: 0; +} + + +// Margin to space out fieldsets +.control-group { + margin-bottom: @baseLineHeight / 2; +} + +// Legend collapses margin, so next element is responsible for spacing +legend + .control-group { + margin-top: @baseLineHeight; + -webkit-margin-top-collapse: separate; +} + +// Horizontal-specific styles +// -------------------------- + +.form-horizontal { + // Increase spacing between groups + .control-group { + margin-bottom: @baseLineHeight; + .clearfix(); + } + // Float the labels left + .control-label { + float: left; + width: @horizontalComponentOffset - 20; + padding-top: 5px; + text-align: right; + } + // Move over all input controls and content + .controls { + // Super jank IE7 fix to ensure the inputs in .input-append and input-prepend + // don't inherit the margin of the parent, in this case .controls + *display: inline-block; + *padding-left: 20px; + margin-left: @horizontalComponentOffset; + *margin-left: 0; + &:first-child { + *padding-left: @horizontalComponentOffset; + } + } + // Remove bottom margin on block level help text since that's accounted for on .control-group + .help-block { + margin-bottom: 0; + } + // And apply it only to .help-block instances that follow a form control + input, + select, + textarea, + .uneditable-input, + .input-prepend, + .input-append { + + .help-block { + margin-top: @baseLineHeight / 2; + } + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: @horizontalComponentOffset; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/grid.less b/openid-connect-server-webapp/src/main/webapp/less/grid.less new file mode 100644 index 0000000000..750d203514 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/grid.less @@ -0,0 +1,21 @@ +// +// Grid system +// -------------------------------------------------- + + +// Fixed (940px) +#grid > .core(@gridColumnWidth, @gridGutterWidth); + +// Fluid (940px) +#grid > .fluid(@fluidGridColumnWidth, @fluidGridGutterWidth); + +// Reset utility classes due to specificity +[class*="span"].hide, +.row-fluid [class*="span"].hide { + display: none; +} + +[class*="span"].pull-right, +.row-fluid [class*="span"].pull-right { + float: right; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/hero-unit.less b/openid-connect-server-webapp/src/main/webapp/less/hero-unit.less new file mode 100644 index 0000000000..763d86aeee --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/hero-unit.less @@ -0,0 +1,25 @@ +// +// Hero unit +// -------------------------------------------------- + + +.hero-unit { + padding: 60px; + margin-bottom: 30px; + font-size: 18px; + font-weight: 200; + line-height: @baseLineHeight * 1.5; + color: @heroUnitLeadColor; + background-color: @heroUnitBackground; + .border-radius(6px); + h1 { + margin-bottom: 0; + font-size: 60px; + line-height: 1; + color: @heroUnitHeadingColor; + letter-spacing: -1px; + } + li { + line-height: @baseLineHeight * 1.5; // Reset since we specify in type.less + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/labels-badges.less b/openid-connect-server-webapp/src/main/webapp/less/labels-badges.less new file mode 100644 index 0000000000..bc321fe5c1 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/labels-badges.less @@ -0,0 +1,84 @@ +// +// Labels and badges +// -------------------------------------------------- + + +// Base classes +.label, +.badge { + display: inline-block; + padding: 2px 4px; + font-size: @baseFontSize * .846; + font-weight: bold; + line-height: 14px; // ensure proper line-height if floated + color: @white; + vertical-align: baseline; + white-space: nowrap; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + background-color: @grayLight; +} +// Set unique padding and border-radii +.label { + .border-radius(3px); +} +.badge { + padding-left: 9px; + padding-right: 9px; + .border-radius(9px); +} + +// Empty labels/badges collapse +.label, +.badge { + &:empty { + display: none; + } +} + +// Hover/focus state, but only for links +a { + &.label:hover, + &.label:focus, + &.badge:hover, + &.badge:focus { + color: @white; + text-decoration: none; + cursor: pointer; + } +} + +// Colors +// Only give background-color difference to links (and to simplify, we don't qualifty with `a` but [href] attribute) +.label, +.badge { + // Important (red) + &-important { background-color: @errorText; } + &-important[href] { background-color: darken(@errorText, 10%); } + // Warnings (orange) + &-warning { background-color: @orange; } + &-warning[href] { background-color: darken(@orange, 10%); } + // Success (green) + &-success { background-color: @successText; } + &-success[href] { background-color: darken(@successText, 10%); } + // Info (turquoise) + &-info { background-color: @infoText; } + &-info[href] { background-color: darken(@infoText, 10%); } + // Inverse (black) + &-inverse { background-color: @grayDark; } + &-inverse[href] { background-color: darken(@grayDark, 10%); } +} + +// Quick fix for labels/badges in buttons +.btn { + .label, + .badge { + position: relative; + top: -1px; + } +} +.btn-mini { + .label, + .badge { + top: 0; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/layouts.less b/openid-connect-server-webapp/src/main/webapp/less/layouts.less new file mode 100644 index 0000000000..24a2062117 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/layouts.less @@ -0,0 +1,16 @@ +// +// Layouts +// -------------------------------------------------- + + +// Container (centered, fixed-width layouts) +.container { + .container-fixed(); +} + +// Fluid layouts (left aligned, with sidebar, min- & max-width content) +.container-fluid { + padding-right: @gridGutterWidth; + padding-left: @gridGutterWidth; + .clearfix(); +} \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/less/media.less b/openid-connect-server-webapp/src/main/webapp/less/media.less new file mode 100644 index 0000000000..e461e446d2 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/media.less @@ -0,0 +1,55 @@ +// Media objects +// Source: http://stubbornella.org/content/?p=497 +// -------------------------------------------------- + + +// Common styles +// ------------------------- + +// Clear the floats +.media, +.media-body { + overflow: hidden; + *overflow: visible; + zoom: 1; +} + +// Proper spacing between instances of .media +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} + +// For images and videos, set to block +.media-object { + display: block; +} + +// Reset margins on headings for tighter default spacing +.media-heading { + margin: 0 0 5px; +} + + +// Media image alignment +// ------------------------- + +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-right { + margin-left: 10px; +} + + +// Media list variation +// ------------------------- + +// Undo default ul/ol styles +.media-list { + margin-left: 0; + list-style: none; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/mixins.less b/openid-connect-server-webapp/src/main/webapp/less/mixins.less new file mode 100644 index 0000000000..857561e9a0 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/mixins.less @@ -0,0 +1,702 @@ +// +// Mixins +// -------------------------------------------------- + + +// UTILITY MIXINS +// -------------------------------------------------- + +// Clearfix +// -------- +// For clearing floats like a boss h5bp.com/q +.clearfix { + *zoom: 1; + &:before, + &:after { + display: table; + content: ""; + // Fixes Opera/contenteditable bug: + // http://nicolasgallagher.com/micro-clearfix-hack/#comment-36952 + line-height: 0; + } + &:after { + clear: both; + } +} + +// Webkit-style focus +// ------------------ +.tab-focus() { + // Default + outline: thin dotted #333; + // Webkit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} + +// Center-align a block level element +// ---------------------------------- +.center-block() { + display: block; + margin-left: auto; + margin-right: auto; +} + +// IE7 inline-block +// ---------------- +.ie7-inline-block() { + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; +} + +// IE7 likes to collapse whitespace on either side of the inline-block elements. +// Ems because we're attempting to match the width of a space character. Left +// version is for form buttons, which typically come after other elements, and +// right version is for icons, which come before. Applying both is ok, but it will +// mean that space between those elements will be .6em (~2 space characters) in IE7, +// instead of the 1 space in other browsers. +.ie7-restore-left-whitespace() { + *margin-left: .3em; + + &:first-child { + *margin-left: 0; + } +} + +.ie7-restore-right-whitespace() { + *margin-right: .3em; +} + +// Sizing shortcuts +// ------------------------- +.size(@height, @width) { + width: @width; + height: @height; +} +.square(@size) { + .size(@size, @size); +} + +// Placeholder text +// ------------------------- +.placeholder(@color: @placeholderText) { + &:-moz-placeholder { + color: @color; + } + &:-ms-input-placeholder { + color: @color; + } + &::-webkit-input-placeholder { + color: @color; + } +} + +// Text overflow +// ------------------------- +// Requires inline-block or block for proper styling +.text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +// CSS image replacement +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757 +.hide-text { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + + +// FONTS +// -------------------------------------------------- + +#font { + #family { + .serif() { + font-family: @serifFontFamily; + } + .sans-serif() { + font-family: @sansFontFamily; + } + .monospace() { + font-family: @monoFontFamily; + } + } + .shorthand(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + font-size: @size; + font-weight: @weight; + line-height: @lineHeight; + } + .serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .sans-serif(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .sans-serif; + #font > .shorthand(@size, @weight, @lineHeight); + } + .monospace(@size: @baseFontSize, @weight: normal, @lineHeight: @baseLineHeight) { + #font > #family > .monospace; + #font > .shorthand(@size, @weight, @lineHeight); + } +} + + +// FORMS +// -------------------------------------------------- + +// Block level inputs +.input-block-level { + display: block; + width: 100%; + min-height: @inputHeight; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + .box-sizing(border-box); // Makes inputs behave like true block-level elements +} + + + +// Mixin for form field states +.formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { + // Set the text color + .control-label, + .help-block, + .help-inline { + color: @textColor; + } + // Style inputs accordingly + .checkbox, + .radio, + input, + select, + textarea { + color: @textColor; + } + input, + select, + textarea { + border-color: @borderColor; + .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken(@borderColor, 10%); + @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@borderColor, 20%); + .box-shadow(@shadow); + } + } + // Give a small background color for input-prepend/-append + .input-prepend .add-on, + .input-append .add-on { + color: @textColor; + background-color: @backgroundColor; + border-color: @textColor; + } +} + + + +// CSS3 PROPERTIES +// -------------------------------------------------- + +// Border Radius +.border-radius(@radius) { + -webkit-border-radius: @radius; + -moz-border-radius: @radius; + border-radius: @radius; +} + +// Single Corner Border Radius +.border-top-left-radius(@radius) { + -webkit-border-top-left-radius: @radius; + -moz-border-radius-topleft: @radius; + border-top-left-radius: @radius; +} +.border-top-right-radius(@radius) { + -webkit-border-top-right-radius: @radius; + -moz-border-radius-topright: @radius; + border-top-right-radius: @radius; +} +.border-bottom-right-radius(@radius) { + -webkit-border-bottom-right-radius: @radius; + -moz-border-radius-bottomright: @radius; + border-bottom-right-radius: @radius; +} +.border-bottom-left-radius(@radius) { + -webkit-border-bottom-left-radius: @radius; + -moz-border-radius-bottomleft: @radius; + border-bottom-left-radius: @radius; +} + +// Single Side Border Radius +.border-top-radius(@radius) { + .border-top-right-radius(@radius); + .border-top-left-radius(@radius); +} +.border-right-radius(@radius) { + .border-top-right-radius(@radius); + .border-bottom-right-radius(@radius); +} +.border-bottom-radius(@radius) { + .border-bottom-right-radius(@radius); + .border-bottom-left-radius(@radius); +} +.border-left-radius(@radius) { + .border-top-left-radius(@radius); + .border-bottom-left-radius(@radius); +} + +// Drop shadows +.box-shadow(@shadow) { + -webkit-box-shadow: @shadow; + -moz-box-shadow: @shadow; + box-shadow: @shadow; +} + +// Transitions +.transition(@transition) { + -webkit-transition: @transition; + -moz-transition: @transition; + -o-transition: @transition; + transition: @transition; +} +.transition-delay(@transition-delay) { + -webkit-transition-delay: @transition-delay; + -moz-transition-delay: @transition-delay; + -o-transition-delay: @transition-delay; + transition-delay: @transition-delay; +} +.transition-duration(@transition-duration) { + -webkit-transition-duration: @transition-duration; + -moz-transition-duration: @transition-duration; + -o-transition-duration: @transition-duration; + transition-duration: @transition-duration; +} + +// Transformations +.rotate(@degrees) { + -webkit-transform: rotate(@degrees); + -moz-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + -o-transform: rotate(@degrees); + transform: rotate(@degrees); +} +.scale(@ratio) { + -webkit-transform: scale(@ratio); + -moz-transform: scale(@ratio); + -ms-transform: scale(@ratio); + -o-transform: scale(@ratio); + transform: scale(@ratio); +} +.translate(@x, @y) { + -webkit-transform: translate(@x, @y); + -moz-transform: translate(@x, @y); + -ms-transform: translate(@x, @y); + -o-transform: translate(@x, @y); + transform: translate(@x, @y); +} +.skew(@x, @y) { + -webkit-transform: skew(@x, @y); + -moz-transform: skew(@x, @y); + -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885 + -o-transform: skew(@x, @y); + transform: skew(@x, @y); + -webkit-backface-visibility: hidden; // See https://github.com/twbs/bootstrap/issues/5319 +} +.translate3d(@x, @y, @z) { + -webkit-transform: translate3d(@x, @y, @z); + -moz-transform: translate3d(@x, @y, @z); + -o-transform: translate3d(@x, @y, @z); + transform: translate3d(@x, @y, @z); +} + +// Backface visibility +// Prevent browsers from flickering when using CSS 3D transforms. +// Default value is `visible`, but can be changed to `hidden +// See git pull https://github.com/dannykeane/bootstrap.git backface-visibility for examples +.backface-visibility(@visibility){ + -webkit-backface-visibility: @visibility; + -moz-backface-visibility: @visibility; + backface-visibility: @visibility; +} + +// Background clipping +// Heads up: FF 3.6 and under need "padding" instead of "padding-box" +.background-clip(@clip) { + -webkit-background-clip: @clip; + -moz-background-clip: @clip; + background-clip: @clip; +} + +// Background sizing +.background-size(@size) { + -webkit-background-size: @size; + -moz-background-size: @size; + -o-background-size: @size; + background-size: @size; +} + + +// Box sizing +.box-sizing(@boxmodel) { + -webkit-box-sizing: @boxmodel; + -moz-box-sizing: @boxmodel; + box-sizing: @boxmodel; +} + +// User select +// For selecting text on the page +.user-select(@select) { + -webkit-user-select: @select; + -moz-user-select: @select; + -ms-user-select: @select; + -o-user-select: @select; + user-select: @select; +} + +// Resize anything +.resizable(@direction) { + resize: @direction; // Options: horizontal, vertical, both + overflow: auto; // Safari fix +} + +// CSS3 Content Columns +.content-columns(@columnCount, @columnGap: @gridGutterWidth) { + -webkit-column-count: @columnCount; + -moz-column-count: @columnCount; + column-count: @columnCount; + -webkit-column-gap: @columnGap; + -moz-column-gap: @columnGap; + column-gap: @columnGap; +} + +// Optional hyphenation +.hyphens(@mode: auto) { + word-wrap: break-word; + -webkit-hyphens: @mode; + -moz-hyphens: @mode; + -ms-hyphens: @mode; + -o-hyphens: @mode; + hyphens: @mode; +} + +// Opacity +.opacity(@opacity) { + opacity: @opacity / 100; + filter: ~"alpha(opacity=@{opacity})"; +} + + + +// BACKGROUNDS +// -------------------------------------------------- + +// Add an alphatransparency value to any background or border color (via Elyse Holladay) +#translucent { + .background(@color: @white, @alpha: 1) { + background-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + } + .border(@color: @white, @alpha: 1) { + border-color: hsla(hue(@color), saturation(@color), lightness(@color), @alpha); + .background-clip(padding-box); + } +} + +// Gradient Bar Colors for buttons and alerts +.gradientBar(@primaryColor, @secondaryColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + color: @textColor; + text-shadow: @textShadow; + #gradient > .vertical(@primaryColor, @secondaryColor); + border-color: @secondaryColor @secondaryColor darken(@secondaryColor, 15%); + border-color: rgba(0,0,0,.1) rgba(0,0,0,.1) fadein(rgba(0,0,0,.1), 15%); +} + +// Gradients +#gradient { + .horizontal(@startColor: #555, @endColor: #333) { + background-color: @endColor; + background-image: -moz-linear-gradient(left, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 100% 0, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(left, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(left, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to right, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .vertical(@startColor: #555, @endColor: #333) { + background-color: mix(@startColor, @endColor, 60%); + background-image: -moz-linear-gradient(top, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), to(@endColor)); // Safari 4+, Chrome 2+ + background-image: -webkit-linear-gradient(top, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(top, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(to bottom, @startColor, @endColor); // Standard, IE10 + background-repeat: repeat-x; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down + } + .directional(@startColor: #555, @endColor: #333, @deg: 45deg) { + background-color: @endColor; + background-repeat: repeat-x; + background-image: -moz-linear-gradient(@deg, @startColor, @endColor); // FF 3.6+ + background-image: -webkit-linear-gradient(@deg, @startColor, @endColor); // Safari 5.1+, Chrome 10+ + background-image: -o-linear-gradient(@deg, @startColor, @endColor); // Opera 11.10 + background-image: linear-gradient(@deg, @startColor, @endColor); // Standard, IE10 + } + .horizontal-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(left, linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(left, @startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(to right, @startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + + .vertical-three-colors(@startColor: #00b3ee, @midColor: #7a43b6, @colorStop: 50%, @endColor: #c3325f) { + background-color: mix(@midColor, @endColor, 80%); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(@startColor), color-stop(@colorStop, @midColor), to(@endColor)); + background-image: -webkit-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: -moz-linear-gradient(top, @startColor, @midColor @colorStop, @endColor); + background-image: -o-linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-image: linear-gradient(@startColor, @midColor @colorStop, @endColor); + background-repeat: no-repeat; + filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)",argb(@startColor),argb(@endColor))); // IE9 and down, gets no color-stop at all for proper fallback + } + .radial(@innerColor: #555, @outerColor: #333) { + background-color: @outerColor; + background-image: -webkit-gradient(radial, center center, 0, center center, 460, from(@innerColor), to(@outerColor)); + background-image: -webkit-radial-gradient(circle, @innerColor, @outerColor); + background-image: -moz-radial-gradient(circle, @innerColor, @outerColor); + background-image: -o-radial-gradient(circle, @innerColor, @outerColor); + background-repeat: no-repeat; + } + .striped(@color: #555, @angle: 45deg) { + background-color: @color; + background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, rgba(255,255,255,.15)), color-stop(.25, transparent), color-stop(.5, transparent), color-stop(.5, rgba(255,255,255,.15)), color-stop(.75, rgba(255,255,255,.15)), color-stop(.75, transparent), to(transparent)); + background-image: -webkit-linear-gradient(@angle, 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: -moz-linear-gradient(@angle, 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(@angle, 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(@angle, 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); + } +} +// Reset filters for IE +.reset-filter() { + filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)")); +} + + + +// COMPONENT MIXINS +// -------------------------------------------------- + +// Horizontal dividers +// ------------------------- +// Dividers (basically an hr) within dropdowns and nav lists +.nav-divider(@top: #e5e5e5, @bottom: @white) { + // IE7 needs a set width since we gave a height. Restricting just + // to IE7 to keep the 1px left/right space in other browsers. + // It is unclear where IE is getting the extra space that we need + // to negative-margin away, but so it goes. + *width: 100%; + height: 1px; + margin: ((@baseLineHeight / 2) - 1) 1px; // 8px 1px + *margin: -5px 0 5px; + overflow: hidden; + background-color: @top; + border-bottom: 1px solid @bottom; +} + +// Button backgrounds +// ------------------ +.buttonBackground(@startColor, @endColor, @textColor: #fff, @textShadow: 0 -1px 0 rgba(0,0,0,.25)) { + // gradientBar will set the background to a pleasing blend of these, to support IE<=9 + .gradientBar(@startColor, @endColor, @textColor, @textShadow); + *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ + .reset-filter(); + + // in these cases the gradient won't cover the background, so we override + &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { + color: @textColor; + background-color: @endColor; + *background-color: darken(@endColor, 5%); + } + + // IE 7 + 8 can't handle box-shadow to show active, so we darken a bit ourselves + &:active, + &.active { + background-color: darken(@endColor, 10%) e("\9"); + } +} + +// Navbar vertical align +// ------------------------- +// Vertically center elements in the navbar. +// Example: an element has a height of 30px, so write out `.navbarVerticalAlign(30px);` to calculate the appropriate top margin. +.navbarVerticalAlign(@elementHeight) { + margin-top: (@navbarHeight - @elementHeight) / 2; +} + + + +// Grid System +// ----------- + +// Centered container element +.container-fixed() { + margin-right: auto; + margin-left: auto; + .clearfix(); +} + +// Table columns +.tableColumns(@columnSpan: 1) { + float: none; // undo default grid column styles + width: ((@gridColumnWidth) * @columnSpan) + (@gridGutterWidth * (@columnSpan - 1)) - 16; // 16 is total padding on left and right of table cells + margin-left: 0; // undo default grid column styles +} + +// Make a Grid +// Use .makeRow and .makeColumn to assign semantic layouts grid system behavior +.makeRow() { + margin-left: @gridGutterWidth * -1; + .clearfix(); +} +.makeColumn(@columns: 1, @offset: 0) { + float: left; + margin-left: (@gridColumnWidth * @offset) + (@gridGutterWidth * (@offset - 1)) + (@gridGutterWidth * 2); + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); +} + +// The Grid +#grid { + + .core (@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns + 1)); + } + + .span (@columns) { + width: (@gridColumnWidth * @columns) + (@gridGutterWidth * (@columns - 1)); + } + + .row { + margin-left: @gridGutterWidth * -1; + .clearfix(); + } + + [class*="span"] { + float: left; + min-height: 1px; // prevent collapsing columns + margin-left: @gridGutterWidth; + } + + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container { .span(@gridColumns); } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + + } + + .fluid (@fluidGridColumnWidth, @fluidGridGutterWidth) { + + .spanX (@index) when (@index > 0) { + .span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .offsetX (@index) when (@index > 0) { + .offset@{index} { .offset(@index); } + .offset@{index}:first-child { .offsetFirstChild(@index); } + .offsetX(@index - 1); + } + .offsetX (0) {} + + .offset (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth*2); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + (@fluidGridGutterWidth*2) - (.5 / @gridRowWidth * 100 * 1%); + } + + .offsetFirstChild (@columns) { + margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) + (@fluidGridGutterWidth); + *margin-left: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%) + @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + + .span (@columns) { + width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)); + *width: (@fluidGridColumnWidth * @columns) + (@fluidGridGutterWidth * (@columns - 1)) - (.5 / @gridRowWidth * 100 * 1%); + } + + .row-fluid { + width: 100%; + .clearfix(); + [class*="span"] { + .input-block-level(); + float: left; + margin-left: @fluidGridGutterWidth; + *margin-left: @fluidGridGutterWidth - (.5 / @gridRowWidth * 100 * 1%); + } + [class*="span"]:first-child { + margin-left: 0; + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @fluidGridGutterWidth; + } + + // generate .spanX and .offsetX + .spanX (@gridColumns); + .offsetX (@gridColumns); + } + + } + + .input(@gridColumnWidth, @gridGutterWidth) { + + .spanX (@index) when (@index > 0) { + input.span@{index}, textarea.span@{index}, .uneditable-input.span@{index} { .span(@index); } + .spanX(@index - 1); + } + .spanX (0) {} + + .span(@columns) { + width: ((@gridColumnWidth) * @columns) + (@gridGutterWidth * (@columns - 1)) - 14; + } + + input, + textarea, + .uneditable-input { + margin-left: 0; // override margin-left from core grid system + } + + // Space grid-sized controls properly if multiple per line + .controls-row [class*="span"] + [class*="span"] { + margin-left: @gridGutterWidth; + } + + // generate .spanX + .spanX (@gridColumns); + + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/modals.less b/openid-connect-server-webapp/src/main/webapp/less/modals.less new file mode 100644 index 0000000000..8e272d409f --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/modals.less @@ -0,0 +1,95 @@ +// +// Modals +// -------------------------------------------------- + +// Background +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: @zindexModalBackdrop; + background-color: @black; + // Fade for backdrop + &.fade { opacity: 0; } +} + +.modal-backdrop, +.modal-backdrop.fade.in { + .opacity(80); +} + +// Base modal +.modal { + position: fixed; + top: 10%; + left: 50%; + z-index: @zindexModal; + width: 560px; + margin-left: -280px; + background-color: @white; + border: 1px solid #999; + border: 1px solid rgba(0,0,0,.3); + *border: 1px solid #999; /* IE6-7 */ + .border-radius(6px); + .box-shadow(0 3px 7px rgba(0,0,0,0.3)); + .background-clip(padding-box); + // Remove focus outline from opened modal + outline: none; + + &.fade { + .transition(e('opacity .3s linear, top .3s ease-out')); + top: -25%; + } + &.fade.in { top: 10%; } +} +.modal-header { + padding: 9px 15px; + border-bottom: 1px solid #eee; + // Close icon + .close { margin-top: 2px; } + // Heading + h3 { + margin: 0; + line-height: 30px; + } +} + +// Body (where all modal content resides) +.modal-body { + position: relative; + overflow-y: auto; + max-height: 400px; + padding: 15px; +} +// Remove bottom margin if need be +.modal-form { + margin-bottom: 0; +} + +// Footer (for actions) +.modal-footer { + padding: 14px 15px 15px; + margin-bottom: 0; + text-align: right; // right align buttons + background-color: #f5f5f5; + border-top: 1px solid #ddd; + .border-radius(0 0 6px 6px); + .box-shadow(inset 0 1px 0 @white); + .clearfix(); // clear it in case folks use .pull-* classes on buttons + + // Properly space out buttons + .btn + .btn { + margin-left: 5px; + margin-bottom: 0; // account for input[type="submit"] which gets the bottom margin like all other inputs + } + // but override that for button groups + .btn-group .btn + .btn { + margin-left: -1px; + } + // and override it for block buttons as well + .btn-block + .btn-block { + margin-left: 0; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/navbar.less b/openid-connect-server-webapp/src/main/webapp/less/navbar.less new file mode 100644 index 0000000000..93d09bcad0 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/navbar.less @@ -0,0 +1,497 @@ +// +// Navbars (Redux) +// -------------------------------------------------- + + +// COMMON STYLES +// ------------- + +// Base class and wrapper +.navbar { + overflow: visible; + margin-bottom: @baseLineHeight; + + // Fix for IE7's bad z-indexing so dropdowns don't appear below content that follows the navbar + *position: relative; + *z-index: 2; +} + +// Inner for background effects +// Gradient is applied to its own element because overflow visible is not honored by IE when filter is present +.navbar-inner { + min-height: @navbarHeight; + padding-left: 20px; + padding-right: 20px; + #gradient > .vertical(@navbarBackgroundHighlight, @navbarBackground); + border: 1px solid @navbarBorder; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 4px rgba(0,0,0,.065)); + + // Prevent floats from breaking the navbar + .clearfix(); +} + +// Set width to auto for default container +// We then reset it for fixed navbars in the #gridSystem mixin +.navbar .container { + width: auto; +} + +// Override the default collapsed state +.nav-collapse.collapse { + height: auto; + overflow: visible; +} + + +// Brand: website or project name +// ------------------------- +.navbar .brand { + float: left; + display: block; + // Vertically center the text given @navbarHeight + padding: ((@navbarHeight - @baseLineHeight) / 2) 20px ((@navbarHeight - @baseLineHeight) / 2); + margin-left: -20px; // negative indent to left-align the text down the page + font-size: 20px; + font-weight: 200; + color: @navbarBrandColor; + text-shadow: 0 1px 0 @navbarBackgroundHighlight; + &:hover, + &:focus { + text-decoration: none; + } +} + +// Plain text in topbar +// ------------------------- +.navbar-text { + margin-bottom: 0; + line-height: @navbarHeight; + color: @navbarText; +} + +// Janky solution for now to account for links outside the .nav +// ------------------------- +.navbar-link { + color: @navbarLinkColor; + &:hover, + &:focus { + color: @navbarLinkColorHover; + } +} + +// Dividers in navbar +// ------------------------- +.navbar .divider-vertical { + height: @navbarHeight; + margin: 0 9px; + border-left: 1px solid @navbarBackground; + border-right: 1px solid @navbarBackgroundHighlight; +} + +// Buttons in navbar +// ------------------------- +.navbar .btn, +.navbar .btn-group { + .navbarVerticalAlign(30px); // Vertically center in navbar +} +.navbar .btn-group .btn, +.navbar .input-prepend .btn, +.navbar .input-append .btn, +.navbar .input-prepend .btn-group, +.navbar .input-append .btn-group { + margin-top: 0; // then undo the margin here so we don't accidentally double it +} + +// Navbar forms +// ------------------------- +.navbar-form { + margin-bottom: 0; // remove default bottom margin + .clearfix(); + input, + select, + .radio, + .checkbox { + .navbarVerticalAlign(30px); // Vertically center in navbar + } + input, + select, + .btn { + display: inline-block; + margin-bottom: 0; + } + input[type="image"], + input[type="checkbox"], + input[type="radio"] { + margin-top: 3px; + } + .input-append, + .input-prepend { + margin-top: 5px; + white-space: nowrap; // preven two items from separating within a .navbar-form that has .pull-left + input { + margin-top: 0; // remove the margin on top since it's on the parent + } + } +} + +// Navbar search +// ------------------------- +.navbar-search { + position: relative; + float: left; + .navbarVerticalAlign(30px); // Vertically center in navbar + margin-bottom: 0; + .search-query { + margin-bottom: 0; + padding: 4px 14px; + #font > .sans-serif(13px, normal, 1); + .border-radius(15px); // redeclare because of specificity of the type attribute + } +} + + + +// Static navbar +// ------------------------- + +.navbar-static-top { + position: static; + margin-bottom: 0; // remove 18px margin for default navbar + .navbar-inner { + .border-radius(0); + } +} + + + +// Fixed navbar +// ------------------------- + +// Shared (top/bottom) styles +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: @zindexFixedNavbar; + margin-bottom: 0; // remove 18px margin for default navbar +} +.navbar-fixed-top .navbar-inner, +.navbar-static-top .navbar-inner { + border-width: 0 0 1px; +} +.navbar-fixed-bottom .navbar-inner { + border-width: 1px 0 0; +} +.navbar-fixed-top .navbar-inner, +.navbar-fixed-bottom .navbar-inner { + padding-left: 0; + padding-right: 0; + .border-radius(0); +} + +// Reset container width +// Required here as we reset the width earlier on and the grid mixins don't override early enough +.navbar-static-top .container, +.navbar-fixed-top .container, +.navbar-fixed-bottom .container { + #grid > .core > .span(@gridColumns); +} + +// Fixed to top +.navbar-fixed-top { + top: 0; +} +.navbar-fixed-top, +.navbar-static-top { + .navbar-inner { + .box-shadow(~"0 1px 10px rgba(0,0,0,.1)"); + } +} + +// Fixed to bottom +.navbar-fixed-bottom { + bottom: 0; + .navbar-inner { + .box-shadow(~"0 -1px 10px rgba(0,0,0,.1)"); + } +} + + + +// NAVIGATION +// ---------- + +.navbar .nav { + position: relative; + left: 0; + display: block; + float: left; + margin: 0 10px 0 0; +} +.navbar .nav.pull-right { + float: right; // redeclare due to specificity + margin-right: 0; // remove margin on float right nav +} +.navbar .nav > li { + float: left; +} + +// Links +.navbar .nav > li > a { + float: none; + // Vertically center the text given @navbarHeight + padding: ((@navbarHeight - @baseLineHeight) / 2) 15px ((@navbarHeight - @baseLineHeight) / 2); + color: @navbarLinkColor; + text-decoration: none; + text-shadow: 0 1px 0 @navbarBackgroundHighlight; +} +.navbar .nav .dropdown-toggle .caret { + margin-top: 8px; +} + +// Hover/focus +.navbar .nav > li > a:focus, +.navbar .nav > li > a:hover { + background-color: @navbarLinkBackgroundHover; // "transparent" is default to differentiate :hover/:focus from .active + color: @navbarLinkColorHover; + text-decoration: none; +} + +// Active nav items +.navbar .nav > .active > a, +.navbar .nav > .active > a:hover, +.navbar .nav > .active > a:focus { + color: @navbarLinkColorActive; + text-decoration: none; + background-color: @navbarLinkBackgroundActive; + .box-shadow(inset 0 3px 8px rgba(0,0,0,.125)); +} + +// Navbar button for toggling navbar items in responsive layouts +// These definitions need to come after '.navbar .btn' +.navbar .btn-navbar { + display: none; + float: right; + padding: 7px 10px; + margin-left: 5px; + margin-right: 5px; + .buttonBackground(darken(@navbarBackgroundHighlight, 5%), darken(@navbarBackground, 5%)); + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.075)"); +} +.navbar .btn-navbar .icon-bar { + display: block; + width: 18px; + height: 2px; + background-color: #f5f5f5; + .border-radius(1px); + .box-shadow(0 1px 0 rgba(0,0,0,.25)); +} +.btn-navbar .icon-bar + .icon-bar { + margin-top: 3px; +} + + + +// Dropdown menus +// -------------- + +// Menu position and menu carets +.navbar .nav > li > .dropdown-menu { + &:before { + content: ''; + display: inline-block; + border-left: 7px solid transparent; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-bottom-color: @dropdownBorder; + position: absolute; + top: -7px; + left: 9px; + } + &:after { + content: ''; + display: inline-block; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid @dropdownBackground; + position: absolute; + top: -6px; + left: 10px; + } +} +// Menu position and menu caret support for dropups via extra dropup class +.navbar-fixed-bottom .nav > li > .dropdown-menu { + &:before { + border-top: 7px solid #ccc; + border-top-color: @dropdownBorder; + border-bottom: 0; + bottom: -7px; + top: auto; + } + &:after { + border-top: 6px solid @dropdownBackground; + border-bottom: 0; + bottom: -6px; + top: auto; + } +} + +// Caret should match text color on hover/focus +.navbar .nav li.dropdown > a:hover .caret, +.navbar .nav li.dropdown > a:focus .caret { + border-top-color: @navbarLinkColorHover; + border-bottom-color: @navbarLinkColorHover; +} + +// Remove background color from open dropdown +.navbar .nav li.dropdown.open > .dropdown-toggle, +.navbar .nav li.dropdown.active > .dropdown-toggle, +.navbar .nav li.dropdown.open.active > .dropdown-toggle { + background-color: @navbarLinkBackgroundActive; + color: @navbarLinkColorActive; +} +.navbar .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: @navbarLinkColor; + border-bottom-color: @navbarLinkColor; +} +.navbar .nav li.dropdown.open > .dropdown-toggle .caret, +.navbar .nav li.dropdown.active > .dropdown-toggle .caret, +.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: @navbarLinkColorActive; + border-bottom-color: @navbarLinkColorActive; +} + +// Right aligned menus need alt position +.navbar .pull-right > li > .dropdown-menu, +.navbar .nav > li > .dropdown-menu.pull-right { + left: auto; + right: 0; + &:before { + left: auto; + right: 12px; + } + &:after { + left: auto; + right: 13px; + } + .dropdown-menu { + left: auto; + right: 100%; + margin-left: 0; + margin-right: -1px; + .border-radius(6px 0 6px 6px); + } +} + + +// Inverted navbar +// ------------------------- + +.navbar-inverse { + + .navbar-inner { + #gradient > .vertical(@navbarInverseBackgroundHighlight, @navbarInverseBackground); + border-color: @navbarInverseBorder; + } + + .brand, + .nav > li > a { + color: @navbarInverseLinkColor; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + &:hover, + &:focus { + color: @navbarInverseLinkColorHover; + } + } + + .brand { + color: @navbarInverseBrandColor; + } + + .navbar-text { + color: @navbarInverseText; + } + + .nav > li > a:focus, + .nav > li > a:hover { + background-color: @navbarInverseLinkBackgroundHover; + color: @navbarInverseLinkColorHover; + } + + .nav .active > a, + .nav .active > a:hover, + .nav .active > a:focus { + color: @navbarInverseLinkColorActive; + background-color: @navbarInverseLinkBackgroundActive; + } + + // Inline text links + .navbar-link { + color: @navbarInverseLinkColor; + &:hover, + &:focus { + color: @navbarInverseLinkColorHover; + } + } + + // Dividers in navbar + .divider-vertical { + border-left-color: @navbarInverseBackground; + border-right-color: @navbarInverseBackgroundHighlight; + } + + // Dropdowns + .nav li.dropdown.open > .dropdown-toggle, + .nav li.dropdown.active > .dropdown-toggle, + .nav li.dropdown.open.active > .dropdown-toggle { + background-color: @navbarInverseLinkBackgroundActive; + color: @navbarInverseLinkColorActive; + } + .nav li.dropdown > a:hover .caret, + .nav li.dropdown > a:focus .caret { + border-top-color: @navbarInverseLinkColorActive; + border-bottom-color: @navbarInverseLinkColorActive; + } + .nav li.dropdown > .dropdown-toggle .caret { + border-top-color: @navbarInverseLinkColor; + border-bottom-color: @navbarInverseLinkColor; + } + .nav li.dropdown.open > .dropdown-toggle .caret, + .nav li.dropdown.active > .dropdown-toggle .caret, + .nav li.dropdown.open.active > .dropdown-toggle .caret { + border-top-color: @navbarInverseLinkColorActive; + border-bottom-color: @navbarInverseLinkColorActive; + } + + // Navbar search + .navbar-search { + .search-query { + color: @white; + background-color: @navbarInverseSearchBackground; + border-color: @navbarInverseSearchBorder; + .box-shadow(~"inset 0 1px 2px rgba(0,0,0,.1), 0 1px 0 rgba(255,255,255,.15)"); + .transition(none); + .placeholder(@navbarInverseSearchPlaceholderColor); + + // Focus states (we use .focused since IE7-8 and down doesn't support :focus) + &:focus, + &.focused { + padding: 5px 15px; + color: @grayDark; + text-shadow: 0 1px 0 @white; + background-color: @navbarInverseSearchBackgroundFocus; + border: 0; + .box-shadow(0 0 3px rgba(0,0,0,.15)); + outline: 0; + } + } + } + + // Navbar collapse button + .btn-navbar { + .buttonBackground(darken(@navbarInverseBackgroundHighlight, 5%), darken(@navbarInverseBackground, 5%)); + } + +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/navs.less b/openid-connect-server-webapp/src/main/webapp/less/navs.less new file mode 100644 index 0000000000..01cd805bde --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/navs.less @@ -0,0 +1,409 @@ +// +// Navs +// -------------------------------------------------- + + +// BASE CLASS +// ---------- + +.nav { + margin-left: 0; + margin-bottom: @baseLineHeight; + list-style: none; +} + +// Make links block level +.nav > li > a { + display: block; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: @grayLighter; +} + +// Prevent IE8 from misplacing imgs +// See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989 +.nav > li > a > img { + max-width: none; +} + +// Redeclare pull classes because of specifity +.nav > .pull-right { + float: right; +} + +// Nav headers (for dropdowns and lists) +.nav-header { + display: block; + padding: 3px 15px; + font-size: 11px; + font-weight: bold; + line-height: @baseLineHeight; + color: @grayLight; + text-shadow: 0 1px 0 rgba(255,255,255,.5); + text-transform: uppercase; +} +// Space them out when they follow another list item (link) +.nav li + .nav-header { + margin-top: 9px; +} + + + +// NAV LIST +// -------- + +.nav-list { + padding-left: 15px; + padding-right: 15px; + margin-bottom: 0; +} +.nav-list > li > a, +.nav-list .nav-header { + margin-left: -15px; + margin-right: -15px; + text-shadow: 0 1px 0 rgba(255,255,255,.5); +} +.nav-list > li > a { + padding: 3px 15px; +} +.nav-list > .active > a, +.nav-list > .active > a:hover, +.nav-list > .active > a:focus { + color: @white; + text-shadow: 0 -1px 0 rgba(0,0,0,.2); + background-color: @linkColor; +} +.nav-list [class^="icon-"], +.nav-list [class*=" icon-"] { + margin-right: 2px; +} +// Dividers (basically an hr) within the dropdown +.nav-list .divider { + .nav-divider(); +} + + + +// TABS AND PILLS +// ------------- + +// Common styles +.nav-tabs, +.nav-pills { + .clearfix(); +} +.nav-tabs > li, +.nav-pills > li { + float: left; +} +.nav-tabs > li > a, +.nav-pills > li > a { + padding-right: 12px; + padding-left: 12px; + margin-right: 2px; + line-height: 14px; // keeps the overall height an even number +} + +// TABS +// ---- + +// Give the tabs something to sit on +.nav-tabs { + border-bottom: 1px solid #ddd; +} +// Make the list-items overlay the bottom border +.nav-tabs > li { + margin-bottom: -1px; +} +// Actual tabs (as links) +.nav-tabs > li > a { + padding-top: 8px; + padding-bottom: 8px; + line-height: @baseLineHeight; + border: 1px solid transparent; + .border-radius(4px 4px 0 0); + &:hover, + &:focus { + border-color: @grayLighter @grayLighter #ddd; + } +} +// Active state, and it's :hover/:focus to override normal :hover/:focus +.nav-tabs > .active > a, +.nav-tabs > .active > a:hover, +.nav-tabs > .active > a:focus { + color: @gray; + background-color: @bodyBackground; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; +} + + +// PILLS +// ----- + +// Links rendered as pills +.nav-pills > li > a { + padding-top: 8px; + padding-bottom: 8px; + margin-top: 2px; + margin-bottom: 2px; + .border-radius(5px); +} + +// Active state +.nav-pills > .active > a, +.nav-pills > .active > a:hover, +.nav-pills > .active > a:focus { + color: @white; + background-color: @linkColor; +} + + + +// STACKED NAV +// ----------- + +// Stacked tabs and pills +.nav-stacked > li { + float: none; +} +.nav-stacked > li > a { + margin-right: 0; // no need for the gap between nav items +} + +// Tabs +.nav-tabs.nav-stacked { + border-bottom: 0; +} +.nav-tabs.nav-stacked > li > a { + border: 1px solid #ddd; + .border-radius(0); +} +.nav-tabs.nav-stacked > li:first-child > a { + .border-top-radius(4px); +} +.nav-tabs.nav-stacked > li:last-child > a { + .border-bottom-radius(4px); +} +.nav-tabs.nav-stacked > li > a:hover, +.nav-tabs.nav-stacked > li > a:focus { + border-color: #ddd; + z-index: 2; +} + +// Pills +.nav-pills.nav-stacked > li > a { + margin-bottom: 3px; +} +.nav-pills.nav-stacked > li:last-child > a { + margin-bottom: 1px; // decrease margin to match sizing of stacked tabs +} + + + +// DROPDOWNS +// --------- + +.nav-tabs .dropdown-menu { + .border-radius(0 0 6px 6px); // remove the top rounded corners here since there is a hard edge above the menu +} +.nav-pills .dropdown-menu { + .border-radius(6px); // make rounded corners match the pills +} + +// Default dropdown links +// ------------------------- +// Make carets use linkColor to start +.nav .dropdown-toggle .caret { + border-top-color: @linkColor; + border-bottom-color: @linkColor; + margin-top: 6px; +} +.nav .dropdown-toggle:hover .caret, +.nav .dropdown-toggle:focus .caret { + border-top-color: @linkColorHover; + border-bottom-color: @linkColorHover; +} +/* move down carets for tabs */ +.nav-tabs .dropdown-toggle .caret { + margin-top: 8px; +} + +// Active dropdown links +// ------------------------- +.nav .active .dropdown-toggle .caret { + border-top-color: #fff; + border-bottom-color: #fff; +} +.nav-tabs .active .dropdown-toggle .caret { + border-top-color: @gray; + border-bottom-color: @gray; +} + +// Active:hover/:focus dropdown links +// ------------------------- +.nav > .dropdown.active > a:hover, +.nav > .dropdown.active > a:focus { + cursor: pointer; +} + +// Open dropdowns +// ------------------------- +.nav-tabs .open .dropdown-toggle, +.nav-pills .open .dropdown-toggle, +.nav > li.dropdown.open.active > a:hover, +.nav > li.dropdown.open.active > a:focus { + color: @white; + background-color: @grayLight; + border-color: @grayLight; +} +.nav li.dropdown.open .caret, +.nav li.dropdown.open.active .caret, +.nav li.dropdown.open a:hover .caret, +.nav li.dropdown.open a:focus .caret { + border-top-color: @white; + border-bottom-color: @white; + .opacity(100); +} + +// Dropdowns in stacked tabs +.tabs-stacked .open > a:hover, +.tabs-stacked .open > a:focus { + border-color: @grayLight; +} + + + +// TABBABLE +// -------- + + +// COMMON STYLES +// ------------- + +// Clear any floats +.tabbable { + .clearfix(); +} +.tab-content { + overflow: auto; // prevent content from running below tabs +} + +// Remove border on bottom, left, right +.tabs-below > .nav-tabs, +.tabs-right > .nav-tabs, +.tabs-left > .nav-tabs { + border-bottom: 0; +} + +// Show/hide tabbable areas +.tab-content > .tab-pane, +.pill-content > .pill-pane { + display: none; +} +.tab-content > .active, +.pill-content > .active { + display: block; +} + + +// BOTTOM +// ------ + +.tabs-below > .nav-tabs { + border-top: 1px solid #ddd; +} +.tabs-below > .nav-tabs > li { + margin-top: -1px; + margin-bottom: 0; +} +.tabs-below > .nav-tabs > li > a { + .border-radius(0 0 4px 4px); + &:hover, + &:focus { + border-bottom-color: transparent; + border-top-color: #ddd; + } +} +.tabs-below > .nav-tabs > .active > a, +.tabs-below > .nav-tabs > .active > a:hover, +.tabs-below > .nav-tabs > .active > a:focus { + border-color: transparent #ddd #ddd #ddd; +} + +// LEFT & RIGHT +// ------------ + +// Common styles +.tabs-left > .nav-tabs > li, +.tabs-right > .nav-tabs > li { + float: none; +} +.tabs-left > .nav-tabs > li > a, +.tabs-right > .nav-tabs > li > a { + min-width: 74px; + margin-right: 0; + margin-bottom: 3px; +} + +// Tabs on the left +.tabs-left > .nav-tabs { + float: left; + margin-right: 19px; + border-right: 1px solid #ddd; +} +.tabs-left > .nav-tabs > li > a { + margin-right: -1px; + .border-radius(4px 0 0 4px); +} +.tabs-left > .nav-tabs > li > a:hover, +.tabs-left > .nav-tabs > li > a:focus { + border-color: @grayLighter #ddd @grayLighter @grayLighter; +} +.tabs-left > .nav-tabs .active > a, +.tabs-left > .nav-tabs .active > a:hover, +.tabs-left > .nav-tabs .active > a:focus { + border-color: #ddd transparent #ddd #ddd; + *border-right-color: @white; +} + +// Tabs on the right +.tabs-right > .nav-tabs { + float: right; + margin-left: 19px; + border-left: 1px solid #ddd; +} +.tabs-right > .nav-tabs > li > a { + margin-left: -1px; + .border-radius(0 4px 4px 0); +} +.tabs-right > .nav-tabs > li > a:hover, +.tabs-right > .nav-tabs > li > a:focus { + border-color: @grayLighter @grayLighter @grayLighter #ddd; +} +.tabs-right > .nav-tabs .active > a, +.tabs-right > .nav-tabs .active > a:hover, +.tabs-right > .nav-tabs .active > a:focus { + border-color: #ddd #ddd #ddd transparent; + *border-left-color: @white; +} + + + +// DISABLED STATES +// --------------- + +// Gray out text +.nav > .disabled > a { + color: @grayLight; +} +// Nuke hover/focus effects +.nav > .disabled > a:hover, +.nav > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + cursor: default; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/pager.less b/openid-connect-server-webapp/src/main/webapp/less/pager.less new file mode 100644 index 0000000000..1476188297 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/pager.less @@ -0,0 +1,43 @@ +// +// Pager pagination +// -------------------------------------------------- + + +.pager { + margin: @baseLineHeight 0; + list-style: none; + text-align: center; + .clearfix(); +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + .border-radius(15px); +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #f5f5f5; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: @grayLight; + background-color: #fff; + cursor: default; +} \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/less/pagination.less b/openid-connect-server-webapp/src/main/webapp/less/pagination.less new file mode 100644 index 0000000000..a789db2d28 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/pagination.less @@ -0,0 +1,123 @@ +// +// Pagination (multiple pages) +// -------------------------------------------------- + +// Space out pagination from surrounding content +.pagination { + margin: @baseLineHeight 0; +} + +.pagination ul { + // Allow for text-based alignment + display: inline-block; + .ie7-inline-block(); + // Reset default ul styles + margin-left: 0; + margin-bottom: 0; + // Visuals + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 2px rgba(0,0,0,.05)); +} +.pagination ul > li { + display: inline; // Remove list-style and block-level defaults +} +.pagination ul > li > a, +.pagination ul > li > span { + float: left; // Collapse white-space + padding: 4px 12px; + line-height: @baseLineHeight; + text-decoration: none; + background-color: @paginationBackground; + border: 1px solid @paginationBorder; + border-left-width: 0; +} +.pagination ul > li > a:hover, +.pagination ul > li > a:focus, +.pagination ul > .active > a, +.pagination ul > .active > span { + background-color: @paginationActiveBackground; +} +.pagination ul > .active > a, +.pagination ul > .active > span { + color: @grayLight; + cursor: default; +} +.pagination ul > .disabled > span, +.pagination ul > .disabled > a, +.pagination ul > .disabled > a:hover, +.pagination ul > .disabled > a:focus { + color: @grayLight; + background-color: transparent; + cursor: default; +} +.pagination ul > li:first-child > a, +.pagination ul > li:first-child > span { + border-left-width: 1px; + .border-left-radius(@baseBorderRadius); +} +.pagination ul > li:last-child > a, +.pagination ul > li:last-child > span { + .border-right-radius(@baseBorderRadius); +} + + +// Alignment +// -------------------------------------------------- + +.pagination-centered { + text-align: center; +} +.pagination-right { + text-align: right; +} + + +// Sizing +// -------------------------------------------------- + +// Large +.pagination-large { + ul > li > a, + ul > li > span { + padding: @paddingLarge; + font-size: @fontSizeLarge; + } + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusLarge); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusLarge); + } +} + +// Small and mini +.pagination-mini, +.pagination-small { + ul > li:first-child > a, + ul > li:first-child > span { + .border-left-radius(@borderRadiusSmall); + } + ul > li:last-child > a, + ul > li:last-child > span { + .border-right-radius(@borderRadiusSmall); + } +} + +// Small +.pagination-small { + ul > li > a, + ul > li > span { + padding: @paddingSmall; + font-size: @fontSizeSmall; + } +} +// Mini +.pagination-mini { + ul > li > a, + ul > li > span { + padding: @paddingMini; + font-size: @fontSizeMini; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/popovers.less b/openid-connect-server-webapp/src/main/webapp/less/popovers.less new file mode 100644 index 0000000000..aae35c8cd5 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/popovers.less @@ -0,0 +1,133 @@ +// +// Popovers +// -------------------------------------------------- + + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: @zindexPopover; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; // Reset given new insertion method + background-color: @popoverBackground; + -webkit-background-clip: padding-box; + -moz-background-clip: padding; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + .border-radius(6px); + .box-shadow(0 5px 10px rgba(0,0,0,.2)); + + // Overrides for proper insertion + white-space: normal; + + // Offset the popover to account for the popover arrow + &.top { margin-top: -10px; } + &.right { margin-left: 10px; } + &.bottom { margin-top: 10px; } + &.left { margin-left: -10px; } +} + +.popover-title { + margin: 0; // reset heading margin + padding: 8px 14px; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: @popoverTitleBackground; + border-bottom: 1px solid darken(@popoverTitleBackground, 5%); + .border-radius(5px 5px 0 0); + + &:empty { + display: none; + } +} + +.popover-content { + padding: 9px 14px; +} + +// Arrows +// +// .arrow is outer, .arrow:after is inner + +.popover .arrow, +.popover .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover .arrow { + border-width: @popoverArrowOuterWidth; +} +.popover .arrow:after { + border-width: @popoverArrowWidth; + content: ""; +} + +.popover { + &.top .arrow { + left: 50%; + margin-left: -@popoverArrowOuterWidth; + border-bottom-width: 0; + border-top-color: #999; // IE8 fallback + border-top-color: @popoverArrowOuterColor; + bottom: -@popoverArrowOuterWidth; + &:after { + bottom: 1px; + margin-left: -@popoverArrowWidth; + border-bottom-width: 0; + border-top-color: @popoverArrowColor; + } + } + &.right .arrow { + top: 50%; + left: -@popoverArrowOuterWidth; + margin-top: -@popoverArrowOuterWidth; + border-left-width: 0; + border-right-color: #999; // IE8 fallback + border-right-color: @popoverArrowOuterColor; + &:after { + left: 1px; + bottom: -@popoverArrowWidth; + border-left-width: 0; + border-right-color: @popoverArrowColor; + } + } + &.bottom .arrow { + left: 50%; + margin-left: -@popoverArrowOuterWidth; + border-top-width: 0; + border-bottom-color: #999; // IE8 fallback + border-bottom-color: @popoverArrowOuterColor; + top: -@popoverArrowOuterWidth; + &:after { + top: 1px; + margin-left: -@popoverArrowWidth; + border-top-width: 0; + border-bottom-color: @popoverArrowColor; + } + } + + &.left .arrow { + top: 50%; + right: -@popoverArrowOuterWidth; + margin-top: -@popoverArrowOuterWidth; + border-right-width: 0; + border-left-color: #999; // IE8 fallback + border-left-color: @popoverArrowOuterColor; + &:after { + right: 1px; + border-right-width: 0; + border-left-color: @popoverArrowColor; + bottom: -@popoverArrowWidth; + } + } + +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/progress-bars.less b/openid-connect-server-webapp/src/main/webapp/less/progress-bars.less new file mode 100644 index 0000000000..5e0c3dda01 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/progress-bars.less @@ -0,0 +1,122 @@ +// +// Progress bars +// -------------------------------------------------- + + +// ANIMATIONS +// ---------- + +// Webkit +@-webkit-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Firefox +@-moz-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// IE9 +@-ms-keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + +// Opera +@-o-keyframes progress-bar-stripes { + from { background-position: 0 0; } + to { background-position: 40px 0; } +} + +// Spec +@keyframes progress-bar-stripes { + from { background-position: 40px 0; } + to { background-position: 0 0; } +} + + + +// THE BARS +// -------- + +// Outer container +.progress { + overflow: hidden; + height: @baseLineHeight; + margin-bottom: @baseLineHeight; + #gradient > .vertical(#f5f5f5, #f9f9f9); + .box-shadow(inset 0 1px 2px rgba(0,0,0,.1)); + .border-radius(@baseBorderRadius); +} + +// Bar of progress +.progress .bar { + width: 0%; + height: 100%; + color: @white; + float: left; + font-size: 12px; + text-align: center; + text-shadow: 0 -1px 0 rgba(0,0,0,.25); + #gradient > .vertical(#149bdf, #0480be); + .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15)); + .box-sizing(border-box); + .transition(width .6s ease); +} +.progress .bar + .bar { + .box-shadow(~"inset 1px 0 0 rgba(0,0,0,.15), inset 0 -1px 0 rgba(0,0,0,.15)"); +} + +// Striped bars +.progress-striped .bar { + #gradient > .striped(#149bdf); + .background-size(40px 40px); +} + +// Call animation for the active one +.progress.active .bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -moz-animation: progress-bar-stripes 2s linear infinite; + -ms-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} + + + +// COLORS +// ------ + +// Danger (red) +.progress-danger .bar, .progress .bar-danger { + #gradient > .vertical(#ee5f5b, #c43c35); +} +.progress-danger.progress-striped .bar, .progress-striped .bar-danger { + #gradient > .striped(#ee5f5b); +} + +// Success (green) +.progress-success .bar, .progress .bar-success { + #gradient > .vertical(#62c462, #57a957); +} +.progress-success.progress-striped .bar, .progress-striped .bar-success { + #gradient > .striped(#62c462); +} + +// Info (teal) +.progress-info .bar, .progress .bar-info { + #gradient > .vertical(#5bc0de, #339bb9); +} +.progress-info.progress-striped .bar, .progress-striped .bar-info { + #gradient > .striped(#5bc0de); +} + +// Warning (orange) +.progress-warning .bar, .progress .bar-warning { + #gradient > .vertical(lighten(@orange, 15%), @orange); +} +.progress-warning.progress-striped .bar, .progress-striped .bar-warning { + #gradient > .striped(lighten(@orange, 15%)); +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/reset.less b/openid-connect-server-webapp/src/main/webapp/less/reset.less new file mode 100644 index 0000000000..4806bd5e59 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/reset.less @@ -0,0 +1,216 @@ +// +// Reset CSS +// Adapted from http://github.com/necolas/normalize.css +// -------------------------------------------------- + + +// Display in IE6-9 and FF3 +// ------------------------- + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +// Display block in IE6-9 and FF3 +// ------------------------- + +audio, +canvas, +video { + display: inline-block; + *display: inline; + *zoom: 1; +} + +// Prevents modern browsers from displaying 'audio' without controls +// ------------------------- + +audio:not([controls]) { + display: none; +} + +// Base settings +// ------------------------- + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +// Focus states +a:focus { + .tab-focus(); +} +// Hover & Active +a:hover, +a:active { + outline: 0; +} + +// Prevents sub and sup affecting line-height in all browsers +// ------------------------- + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} + +// Img border in a's and image quality +// ------------------------- + +img { + /* Responsive images (ensure images don't scale beyond their parents) */ + max-width: 100%; /* Part 1: Set a maxium relative to the parent */ + width: auto\9; /* IE7-8 need help adjusting responsive images */ + height: auto; /* Part 2: Scale the height according to the width, otherwise you get stretching */ + + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; +} + +// Prevent max-width from affecting Google Maps +#map_canvas img, +.google-maps img { + max-width: none; +} + +// Forms +// ------------------------- + +// Font size in all browsers, margin changes, misc consistency +button, +input, +select, +textarea { + margin: 0; + font-size: 100%; + vertical-align: middle; +} +button, +input { + *overflow: visible; // Inner spacing ie IE6/7 + line-height: normal; // FF3/4 have !important on line-height in UA stylesheet +} +button::-moz-focus-inner, +input::-moz-focus-inner { // Inner padding and border oddities in FF3/4 + padding: 0; + border: 0; +} +button, +html input[type="button"], // Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` and `video` controls. +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; // Corrects inability to style clickable `input` types in iOS. + cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. +} +label, +select, +button, +input[type="button"], +input[type="reset"], +input[type="submit"], +input[type="radio"], +input[type="checkbox"] { + cursor: pointer; // Improves usability and consistency of cursor style between image-type `input` and others. +} +input[type="search"] { // Appearance in Safari/Chrome + .box-sizing(content-box); + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; // Inner-padding issues in Chrome OSX, Safari 5 +} +textarea { + overflow: auto; // Remove vertical scrollbar in IE6-9 + vertical-align: top; // Readability and alignment cross-browser +} + + +// Printing +// ------------------------- +// Source: https://github.com/h5bp/html5-boilerplate/blob/master/css/main.css + +@media print { + + * { + text-shadow: none !important; + color: #000 !important; // Black prints faster: h5bp.com/s + background: transparent !important; + box-shadow: none !important; + } + + a, + a:visited { + text-decoration: underline; + } + + a[href]:after { + content: " (" attr(href) ")"; + } + + abbr[title]:after { + content: " (" attr(title) ")"; + } + + // Don't show links for images, or javascript/internal links + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + + pre, + blockquote { + border: 1px solid #999; + page-break-inside: avoid; + } + + thead { + display: table-header-group; // h5bp.com/t + } + + tr, + img { + page-break-inside: avoid; + } + + img { + max-width: 100% !important; + } + + @page { + margin: 0.5cm; + } + + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + + h2, + h3 { + page-break-after: avoid; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/responsive-1200px-min.less b/openid-connect-server-webapp/src/main/webapp/less/responsive-1200px-min.less new file mode 100644 index 0000000000..4f35ba6ca2 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/responsive-1200px-min.less @@ -0,0 +1,28 @@ +// +// Responsive: Large desktop and up +// -------------------------------------------------- + + +@media (min-width: 1200px) { + + // Fixed grid + #grid > .core(@gridColumnWidth1200, @gridGutterWidth1200); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth1200, @fluidGridGutterWidth1200); + + // Input grid + #grid > .input(@gridColumnWidth1200, @gridGutterWidth1200); + + // Thumbnails + .thumbnails { + margin-left: -@gridGutterWidth1200; + } + .thumbnails > li { + margin-left: @gridGutterWidth1200; + } + .row-fluid .thumbnails { + margin-left: 0; + } + +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/responsive-767px-max.less b/openid-connect-server-webapp/src/main/webapp/less/responsive-767px-max.less new file mode 100644 index 0000000000..128f4ce30d --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/responsive-767px-max.less @@ -0,0 +1,193 @@ +// +// Responsive: Landscape phone to desktop/tablet +// -------------------------------------------------- + + +@media (max-width: 767px) { + + // Padding to set content in a bit + body { + padding-left: 20px; + padding-right: 20px; + } + // Negative indent the now static "fixed" navbar + .navbar-fixed-top, + .navbar-fixed-bottom, + .navbar-static-top { + margin-left: -20px; + margin-right: -20px; + } + // Remove padding on container given explicit padding set on body + .container-fluid { + padding: 0; + } + + // TYPOGRAPHY + // ---------- + // Reset horizontal dl + .dl-horizontal { + dt { + float: none; + clear: none; + width: auto; + text-align: left; + } + dd { + margin-left: 0; + } + } + + // GRID & CONTAINERS + // ----------------- + // Remove width from containers + .container { + width: auto; + } + // Fluid rows + .row-fluid { + width: 100%; + } + // Undo negative margin on rows and thumbnails + .row, + .thumbnails { + margin-left: 0; + } + .thumbnails > li { + float: none; + margin-left: 0; // Reset the default margin for all li elements when no .span* classes are present + } + // Make all grid-sized elements block level again + [class*="span"], + .uneditable-input[class*="span"], // Makes uneditable inputs full-width when using grid sizing + .row-fluid [class*="span"] { + float: none; + display: block; + width: 100%; + margin-left: 0; + .box-sizing(border-box); + } + .span12, + .row-fluid .span12 { + width: 100%; + .box-sizing(border-box); + } + .row-fluid [class*="offset"]:first-child { + margin-left: 0; + } + + // FORM FIELDS + // ----------- + // Make span* classes full width + .input-large, + .input-xlarge, + .input-xxlarge, + input[class*="span"], + select[class*="span"], + textarea[class*="span"], + .uneditable-input { + .input-block-level(); + } + // But don't let it screw up prepend/append inputs + .input-prepend input, + .input-append input, + .input-prepend input[class*="span"], + .input-append input[class*="span"] { + display: inline-block; // redeclare so they don't wrap to new lines + width: auto; + } + .controls-row [class*="span"] + [class*="span"] { + margin-left: 0; + } + + // Modals + .modal { + position: fixed; + top: 20px; + left: 20px; + right: 20px; + width: auto; + margin: 0; + &.fade { top: -100px; } + &.fade.in { top: 20px; } + } + +} + + + +// UP TO LANDSCAPE PHONE +// --------------------- + +@media (max-width: 480px) { + + // Smooth out the collapsing/expanding nav + .nav-collapse { + -webkit-transform: translate3d(0, 0, 0); // activate the GPU + } + + // Block level the page header small tag for readability + .page-header h1 small { + display: block; + line-height: @baseLineHeight; + } + + // Update checkboxes for iOS + input[type="checkbox"], + input[type="radio"] { + border: 1px solid #ccc; + } + + // Remove the horizontal form styles + .form-horizontal { + .control-label { + float: none; + width: auto; + padding-top: 0; + text-align: left; + } + // Move over all input controls and content + .controls { + margin-left: 0; + } + // Move the options list down to align with labels + .control-list { + padding-top: 0; // has to be padding because margin collaspes + } + // Move over buttons in .form-actions to align with .controls + .form-actions { + padding-left: 10px; + padding-right: 10px; + } + } + + // Medias + // Reset float and spacing to stack + .media .pull-left, + .media .pull-right { + float: none; + display: block; + margin-bottom: 10px; + } + // Remove side margins since we stack instead of indent + .media-object { + margin-right: 0; + margin-left: 0; + } + + // Modals + .modal { + top: 10px; + left: 10px; + right: 10px; + } + .modal-header .close { + padding: 10px; + margin: -10px; + } + + // Carousel + .carousel-caption { + position: static; + } + +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/responsive-768px-979px.less b/openid-connect-server-webapp/src/main/webapp/less/responsive-768px-979px.less new file mode 100644 index 0000000000..8e8c486a06 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/responsive-768px-979px.less @@ -0,0 +1,19 @@ +// +// Responsive: Tablet to desktop +// -------------------------------------------------- + + +@media (min-width: 768px) and (max-width: 979px) { + + // Fixed grid + #grid > .core(@gridColumnWidth768, @gridGutterWidth768); + + // Fluid grid + #grid > .fluid(@fluidGridColumnWidth768, @fluidGridGutterWidth768); + + // Input grid + #grid > .input(@gridColumnWidth768, @gridGutterWidth768); + + // No need to reset .thumbnails here since it's the same @gridGutterWidth + +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/responsive-navbar.less b/openid-connect-server-webapp/src/main/webapp/less/responsive-navbar.less new file mode 100644 index 0000000000..21cd3ba671 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/responsive-navbar.less @@ -0,0 +1,189 @@ +// +// Responsive: Navbar +// -------------------------------------------------- + + +// TABLETS AND BELOW +// ----------------- +@media (max-width: @navbarCollapseWidth) { + + // UNFIX THE TOPBAR + // ---------------- + // Remove any padding from the body + body { + padding-top: 0; + } + // Unfix the navbars + .navbar-fixed-top, + .navbar-fixed-bottom { + position: static; + } + .navbar-fixed-top { + margin-bottom: @baseLineHeight; + } + .navbar-fixed-bottom { + margin-top: @baseLineHeight; + } + .navbar-fixed-top .navbar-inner, + .navbar-fixed-bottom .navbar-inner { + padding: 5px; + } + .navbar .container { + width: auto; + padding: 0; + } + // Account for brand name + .navbar .brand { + padding-left: 10px; + padding-right: 10px; + margin: 0 0 0 -5px; + } + + // COLLAPSIBLE NAVBAR + // ------------------ + // Nav collapse clears brand + .nav-collapse { + clear: both; + } + // Block-level the nav + .nav-collapse .nav { + float: none; + margin: 0 0 (@baseLineHeight / 2); + } + .nav-collapse .nav > li { + float: none; + } + .nav-collapse .nav > li > a { + margin-bottom: 2px; + } + .nav-collapse .nav > .divider-vertical { + display: none; + } + .nav-collapse .nav .nav-header { + color: @navbarText; + text-shadow: none; + } + // Nav and dropdown links in navbar + .nav-collapse .nav > li > a, + .nav-collapse .dropdown-menu a { + padding: 9px 15px; + font-weight: bold; + color: @navbarLinkColor; + .border-radius(3px); + } + // Buttons + .nav-collapse .btn { + padding: 4px 10px 4px; + font-weight: normal; + .border-radius(@baseBorderRadius); + } + .nav-collapse .dropdown-menu li + li a { + margin-bottom: 2px; + } + .nav-collapse .nav > li > a:hover, + .nav-collapse .nav > li > a:focus, + .nav-collapse .dropdown-menu a:hover, + .nav-collapse .dropdown-menu a:focus { + background-color: @navbarBackground; + } + .navbar-inverse .nav-collapse .nav > li > a, + .navbar-inverse .nav-collapse .dropdown-menu a { + color: @navbarInverseLinkColor; + } + .navbar-inverse .nav-collapse .nav > li > a:hover, + .navbar-inverse .nav-collapse .nav > li > a:focus, + .navbar-inverse .nav-collapse .dropdown-menu a:hover, + .navbar-inverse .nav-collapse .dropdown-menu a:focus { + background-color: @navbarInverseBackground; + } + // Buttons in the navbar + .nav-collapse.in .btn-group { + margin-top: 5px; + padding: 0; + } + // Dropdowns in the navbar + .nav-collapse .dropdown-menu { + position: static; + top: auto; + left: auto; + float: none; + display: none; + max-width: none; + margin: 0 15px; + padding: 0; + background-color: transparent; + border: none; + .border-radius(0); + .box-shadow(none); + } + .nav-collapse .open > .dropdown-menu { + display: block; + } + + .nav-collapse .dropdown-menu:before, + .nav-collapse .dropdown-menu:after { + display: none; + } + .nav-collapse .dropdown-menu .divider { + display: none; + } + .nav-collapse .nav > li > .dropdown-menu { + &:before, + &:after { + display: none; + } + } + // Forms in navbar + .nav-collapse .navbar-form, + .nav-collapse .navbar-search { + float: none; + padding: (@baseLineHeight / 2) 15px; + margin: (@baseLineHeight / 2) 0; + border-top: 1px solid @navbarBackground; + border-bottom: 1px solid @navbarBackground; + .box-shadow(~"inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1)"); + } + .navbar-inverse .nav-collapse .navbar-form, + .navbar-inverse .nav-collapse .navbar-search { + border-top-color: @navbarInverseBackground; + border-bottom-color: @navbarInverseBackground; + } + // Pull right (secondary) nav content + .navbar .nav-collapse .nav.pull-right { + float: none; + margin-left: 0; + } + // Hide everything in the navbar save .brand and toggle button */ + .nav-collapse, + .nav-collapse.collapse { + overflow: hidden; + height: 0; + } + // Navbar button + .navbar .btn-navbar { + display: block; + } + + // STATIC NAVBAR + // ------------- + .navbar-static .navbar-inner { + padding-left: 10px; + padding-right: 10px; + } + + +} + + +// DEFAULT DESKTOP +// --------------- + +@media (min-width: @navbarCollapseDesktopWidth) { + + // Required to make the collapsing navbar work on regular desktops + .nav-collapse.collapse { + height: auto !important; + overflow: visible !important; + } + +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/responsive-utilities.less b/openid-connect-server-webapp/src/main/webapp/less/responsive-utilities.less new file mode 100644 index 0000000000..bf43e8ef73 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/responsive-utilities.less @@ -0,0 +1,59 @@ +// +// Responsive: Utility classes +// -------------------------------------------------- + + +// IE10 Metro responsive +// Required for Windows 8 Metro split-screen snapping with IE10 +// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/ +@-ms-viewport{ + width: device-width; +} + +// Hide from screenreaders and browsers +// Credit: HTML5 Boilerplate +.hidden { + display: none; + visibility: hidden; +} + +// Visibility utilities + +// For desktops +.visible-phone { display: none !important; } +.visible-tablet { display: none !important; } +.hidden-phone { } +.hidden-tablet { } +.hidden-desktop { display: none !important; } +.visible-desktop { display: inherit !important; } + +// Tablets & small desktops only +@media (min-width: 768px) and (max-width: 979px) { + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important ; } + // Show + .visible-tablet { display: inherit !important; } + // Hide + .hidden-tablet { display: none !important; } +} + +// Phones only +@media (max-width: 767px) { + // Hide everything else + .hidden-desktop { display: inherit !important; } + .visible-desktop { display: none !important; } + // Show + .visible-phone { display: inherit !important; } // Use inherit to restore previous behavior + // Hide + .hidden-phone { display: none !important; } +} + +// Print utilities +.visible-print { display: none !important; } +.hidden-print { } + +@media print { + .visible-print { display: inherit !important; } + .hidden-print { display: none !important; } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/scaffolding.less b/openid-connect-server-webapp/src/main/webapp/less/scaffolding.less new file mode 100644 index 0000000000..f17e8cadb4 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/scaffolding.less @@ -0,0 +1,53 @@ +// +// Scaffolding +// -------------------------------------------------- + + +// Body reset +// ------------------------- + +body { + margin: 0; + font-family: @baseFontFamily; + font-size: @baseFontSize; + line-height: @baseLineHeight; + color: @textColor; + background-color: @bodyBackground; +} + + +// Links +// ------------------------- + +a { + color: @linkColor; + text-decoration: none; +} +a:hover, +a:focus { + color: @linkColorHover; + text-decoration: underline; +} + + +// Images +// ------------------------- + +// Rounded corners +.img-rounded { + .border-radius(6px); +} + +// Add polaroid-esque trim +.img-polaroid { + padding: 4px; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0,0,0,.2); + .box-shadow(0 1px 3px rgba(0,0,0,.1)); +} + +// Perfect circle +.img-circle { + .border-radius(500px); // crank the border-radius so it works with most reasonably sized images +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/sprites.less b/openid-connect-server-webapp/src/main/webapp/less/sprites.less new file mode 100644 index 0000000000..1812bf71ac --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/sprites.less @@ -0,0 +1,197 @@ +// +// Sprites +// -------------------------------------------------- + + +// ICONS +// ----- + +// All icons receive the styles of the tag with a base class +// of .i and are then given a unique class to add width, height, +// and background-position. Your resulting HTML will look like +// . + +// For the white version of the icons, just add the .icon-white class: +// + +[class^="icon-"], +[class*=" icon-"] { + display: inline-block; + width: 14px; + height: 14px; + .ie7-restore-right-whitespace(); + line-height: 14px; + vertical-align: text-top; + background-image: url("@{iconSpritePath}"); + background-position: 14px 14px; + background-repeat: no-repeat; + margin-top: 1px; +} + +/* White icons with optional class, or on hover/focus/active states of certain elements */ +.icon-white, +.nav-pills > .active > a > [class^="icon-"], +.nav-pills > .active > a > [class*=" icon-"], +.nav-list > .active > a > [class^="icon-"], +.nav-list > .active > a > [class*=" icon-"], +.navbar-inverse .nav > .active > a > [class^="icon-"], +.navbar-inverse .nav > .active > a > [class*=" icon-"], +.dropdown-menu > li > a:hover > [class^="icon-"], +.dropdown-menu > li > a:focus > [class^="icon-"], +.dropdown-menu > li > a:hover > [class*=" icon-"], +.dropdown-menu > li > a:focus > [class*=" icon-"], +.dropdown-menu > .active > a > [class^="icon-"], +.dropdown-menu > .active > a > [class*=" icon-"], +.dropdown-submenu:hover > a > [class^="icon-"], +.dropdown-submenu:focus > a > [class^="icon-"], +.dropdown-submenu:hover > a > [class*=" icon-"], +.dropdown-submenu:focus > a > [class*=" icon-"] { + background-image: url("@{iconWhiteSpritePath}"); +} + +.icon-glass { background-position: 0 0; } +.icon-music { background-position: -24px 0; } +.icon-search { background-position: -48px 0; } +.icon-envelope { background-position: -72px 0; } +.icon-heart { background-position: -96px 0; } +.icon-star { background-position: -120px 0; } +.icon-star-empty { background-position: -144px 0; } +.icon-user { background-position: -168px 0; } +.icon-film { background-position: -192px 0; } +.icon-th-large { background-position: -216px 0; } +.icon-th { background-position: -240px 0; } +.icon-th-list { background-position: -264px 0; } +.icon-ok { background-position: -288px 0; } +.icon-remove { background-position: -312px 0; } +.icon-zoom-in { background-position: -336px 0; } +.icon-zoom-out { background-position: -360px 0; } +.icon-off { background-position: -384px 0; } +.icon-signal { background-position: -408px 0; } +.icon-cog { background-position: -432px 0; } +.icon-trash { background-position: -456px 0; } + +.icon-home { background-position: 0 -24px; } +.icon-file { background-position: -24px -24px; } +.icon-time { background-position: -48px -24px; } +.icon-road { background-position: -72px -24px; } +.icon-download-alt { background-position: -96px -24px; } +.icon-download { background-position: -120px -24px; } +.icon-upload { background-position: -144px -24px; } +.icon-inbox { background-position: -168px -24px; } +.icon-play-circle { background-position: -192px -24px; } +.icon-repeat { background-position: -216px -24px; } +.icon-refresh { background-position: -240px -24px; } +.icon-list-alt { background-position: -264px -24px; } +.icon-lock { background-position: -287px -24px; } // 1px off +.icon-flag { background-position: -312px -24px; } +.icon-headphones { background-position: -336px -24px; } +.icon-volume-off { background-position: -360px -24px; } +.icon-volume-down { background-position: -384px -24px; } +.icon-volume-up { background-position: -408px -24px; } +.icon-qrcode { background-position: -432px -24px; } +.icon-barcode { background-position: -456px -24px; } + +.icon-tag { background-position: 0 -48px; } +.icon-tags { background-position: -25px -48px; } // 1px off +.icon-book { background-position: -48px -48px; } +.icon-bookmark { background-position: -72px -48px; } +.icon-print { background-position: -96px -48px; } +.icon-camera { background-position: -120px -48px; } +.icon-font { background-position: -144px -48px; } +.icon-bold { background-position: -167px -48px; } // 1px off +.icon-italic { background-position: -192px -48px; } +.icon-text-height { background-position: -216px -48px; } +.icon-text-width { background-position: -240px -48px; } +.icon-align-left { background-position: -264px -48px; } +.icon-align-center { background-position: -288px -48px; } +.icon-align-right { background-position: -312px -48px; } +.icon-align-justify { background-position: -336px -48px; } +.icon-list { background-position: -360px -48px; } +.icon-indent-left { background-position: -384px -48px; } +.icon-indent-right { background-position: -408px -48px; } +.icon-facetime-video { background-position: -432px -48px; } +.icon-picture { background-position: -456px -48px; } + +.icon-pencil { background-position: 0 -72px; } +.icon-map-marker { background-position: -24px -72px; } +.icon-adjust { background-position: -48px -72px; } +.icon-tint { background-position: -72px -72px; } +.icon-edit { background-position: -96px -72px; } +.icon-share { background-position: -120px -72px; } +.icon-check { background-position: -144px -72px; } +.icon-move { background-position: -168px -72px; } +.icon-step-backward { background-position: -192px -72px; } +.icon-fast-backward { background-position: -216px -72px; } +.icon-backward { background-position: -240px -72px; } +.icon-play { background-position: -264px -72px; } +.icon-pause { background-position: -288px -72px; } +.icon-stop { background-position: -312px -72px; } +.icon-forward { background-position: -336px -72px; } +.icon-fast-forward { background-position: -360px -72px; } +.icon-step-forward { background-position: -384px -72px; } +.icon-eject { background-position: -408px -72px; } +.icon-chevron-left { background-position: -432px -72px; } +.icon-chevron-right { background-position: -456px -72px; } + +.icon-plus-sign { background-position: 0 -96px; } +.icon-minus-sign { background-position: -24px -96px; } +.icon-remove-sign { background-position: -48px -96px; } +.icon-ok-sign { background-position: -72px -96px; } +.icon-question-sign { background-position: -96px -96px; } +.icon-info-sign { background-position: -120px -96px; } +.icon-screenshot { background-position: -144px -96px; } +.icon-remove-circle { background-position: -168px -96px; } +.icon-ok-circle { background-position: -192px -96px; } +.icon-ban-circle { background-position: -216px -96px; } +.icon-arrow-left { background-position: -240px -96px; } +.icon-arrow-right { background-position: -264px -96px; } +.icon-arrow-up { background-position: -289px -96px; } // 1px off +.icon-arrow-down { background-position: -312px -96px; } +.icon-share-alt { background-position: -336px -96px; } +.icon-resize-full { background-position: -360px -96px; } +.icon-resize-small { background-position: -384px -96px; } +.icon-plus { background-position: -408px -96px; } +.icon-minus { background-position: -433px -96px; } +.icon-asterisk { background-position: -456px -96px; } + +.icon-exclamation-sign { background-position: 0 -120px; } +.icon-gift { background-position: -24px -120px; } +.icon-leaf { background-position: -48px -120px; } +.icon-fire { background-position: -72px -120px; } +.icon-eye-open { background-position: -96px -120px; } +.icon-eye-close { background-position: -120px -120px; } +.icon-warning-sign { background-position: -144px -120px; } +.icon-plane { background-position: -168px -120px; } +.icon-calendar { background-position: -192px -120px; } +.icon-random { background-position: -216px -120px; width: 16px; } +.icon-comment { background-position: -240px -120px; } +.icon-magnet { background-position: -264px -120px; } +.icon-chevron-up { background-position: -288px -120px; } +.icon-chevron-down { background-position: -313px -119px; } // 1px, 1px off +.icon-retweet { background-position: -336px -120px; } +.icon-shopping-cart { background-position: -360px -120px; } +.icon-folder-close { background-position: -384px -120px; width: 16px; } +.icon-folder-open { background-position: -408px -120px; width: 16px; } +.icon-resize-vertical { background-position: -432px -119px; } // 1px, 1px off +.icon-resize-horizontal { background-position: -456px -118px; } // 1px, 2px off + +.icon-hdd { background-position: 0 -144px; } +.icon-bullhorn { background-position: -24px -144px; } +.icon-bell { background-position: -48px -144px; } +.icon-certificate { background-position: -72px -144px; } +.icon-thumbs-up { background-position: -96px -144px; } +.icon-thumbs-down { background-position: -120px -144px; } +.icon-hand-right { background-position: -144px -144px; } +.icon-hand-left { background-position: -168px -144px; } +.icon-hand-up { background-position: -192px -144px; } +.icon-hand-down { background-position: -216px -144px; } +.icon-circle-arrow-right { background-position: -240px -144px; } +.icon-circle-arrow-left { background-position: -264px -144px; } +.icon-circle-arrow-up { background-position: -288px -144px; } +.icon-circle-arrow-down { background-position: -312px -144px; } +.icon-globe { background-position: -336px -144px; } +.icon-wrench { background-position: -360px -144px; } +.icon-tasks { background-position: -384px -144px; } +.icon-filter { background-position: -408px -144px; } +.icon-briefcase { background-position: -432px -144px; } +.icon-fullscreen { background-position: -456px -144px; } diff --git a/openid-connect-server-webapp/src/main/webapp/less/tables.less b/openid-connect-server-webapp/src/main/webapp/less/tables.less new file mode 100644 index 0000000000..0e35271e11 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/tables.less @@ -0,0 +1,244 @@ +// +// Tables +// -------------------------------------------------- + + +// BASE TABLES +// ----------------- + +table { + max-width: 100%; + background-color: @tableBackground; + border-collapse: collapse; + border-spacing: 0; +} + +// BASELINE STYLES +// --------------- + +.table { + width: 100%; + margin-bottom: @baseLineHeight; + // Cells + th, + td { + padding: 8px; + line-height: @baseLineHeight; + text-align: left; + vertical-align: top; + border-top: 1px solid @tableBorder; + } + th { + font-weight: bold; + } + // Bottom align for column headings + thead th { + vertical-align: bottom; + } + // Remove top border from thead by default + caption + thead tr:first-child th, + caption + thead tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + thead tr:first-child td, + thead:first-child tr:first-child th, + thead:first-child tr:first-child td { + border-top: 0; + } + // Account for multiple tbody instances + tbody + tbody { + border-top: 2px solid @tableBorder; + } + + // Nesting + .table { + background-color: @bodyBackground; + } +} + + + +// CONDENSED TABLE W/ HALF PADDING +// ------------------------------- + +.table-condensed { + th, + td { + padding: 4px 5px; + } +} + + +// BORDERED VERSION +// ---------------- + +.table-bordered { + border: 1px solid @tableBorder; + border-collapse: separate; // Done so we can round those corners! + *border-collapse: collapse; // IE7 can't round corners anyway + border-left: 0; + .border-radius(@baseBorderRadius); + th, + td { + border-left: 1px solid @tableBorder; + } + // Prevent a double border + caption + thead tr:first-child th, + caption + tbody tr:first-child th, + caption + tbody tr:first-child td, + colgroup + thead tr:first-child th, + colgroup + tbody tr:first-child th, + colgroup + tbody tr:first-child td, + thead:first-child tr:first-child th, + tbody:first-child tr:first-child th, + tbody:first-child tr:first-child td { + border-top: 0; + } + // For first th/td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:first-child, + tbody:first-child tr:first-child > td:first-child, + tbody:first-child tr:first-child > th:first-child { + .border-top-left-radius(@baseBorderRadius); + } + // For last th/td in the first row in the first thead or tbody + thead:first-child tr:first-child > th:last-child, + tbody:first-child tr:first-child > td:last-child, + tbody:first-child tr:first-child > th:last-child { + .border-top-right-radius(@baseBorderRadius); + } + // For first th/td (can be either) in the last row in the last thead, tbody, and tfoot + thead:last-child tr:last-child > th:first-child, + tbody:last-child tr:last-child > td:first-child, + tbody:last-child tr:last-child > th:first-child, + tfoot:last-child tr:last-child > td:first-child, + tfoot:last-child tr:last-child > th:first-child { + .border-bottom-left-radius(@baseBorderRadius); + } + // For last th/td (can be either) in the last row in the last thead, tbody, and tfoot + thead:last-child tr:last-child > th:last-child, + tbody:last-child tr:last-child > td:last-child, + tbody:last-child tr:last-child > th:last-child, + tfoot:last-child tr:last-child > td:last-child, + tfoot:last-child tr:last-child > th:last-child { + .border-bottom-right-radius(@baseBorderRadius); + } + + // Clear border-radius for first and last td in the last row in the last tbody for table with tfoot + tfoot + tbody:last-child tr:last-child td:first-child { + .border-bottom-left-radius(0); + } + tfoot + tbody:last-child tr:last-child td:last-child { + .border-bottom-right-radius(0); + } + + // Special fixes to round the left border on the first td/th + caption + thead tr:first-child th:first-child, + caption + tbody tr:first-child td:first-child, + colgroup + thead tr:first-child th:first-child, + colgroup + tbody tr:first-child td:first-child { + .border-top-left-radius(@baseBorderRadius); + } + caption + thead tr:first-child th:last-child, + caption + tbody tr:first-child td:last-child, + colgroup + thead tr:first-child th:last-child, + colgroup + tbody tr:first-child td:last-child { + .border-top-right-radius(@baseBorderRadius); + } + +} + + + + +// ZEBRA-STRIPING +// -------------- + +// Default zebra-stripe styles (alternating gray and transparent backgrounds) +.table-striped { + tbody { + > tr:nth-child(odd) > td, + > tr:nth-child(odd) > th { + background-color: @tableBackgroundAccent; + } + } +} + + +// HOVER EFFECT +// ------------ +// Placed here since it has to come after the potential zebra striping +.table-hover { + tbody { + tr:hover > td, + tr:hover > th { + background-color: @tableBackgroundHover; + } + } +} + + +// TABLE CELL SIZING +// ----------------- + +// Reset default grid behavior +table td[class*="span"], +table th[class*="span"], +.row-fluid table td[class*="span"], +.row-fluid table th[class*="span"] { + display: table-cell; + float: none; // undo default grid column styles + margin-left: 0; // undo default grid column styles +} + +// Change the column widths to account for td/th padding +.table td, +.table th { + &.span1 { .tableColumns(1); } + &.span2 { .tableColumns(2); } + &.span3 { .tableColumns(3); } + &.span4 { .tableColumns(4); } + &.span5 { .tableColumns(5); } + &.span6 { .tableColumns(6); } + &.span7 { .tableColumns(7); } + &.span8 { .tableColumns(8); } + &.span9 { .tableColumns(9); } + &.span10 { .tableColumns(10); } + &.span11 { .tableColumns(11); } + &.span12 { .tableColumns(12); } +} + + + +// TABLE BACKGROUNDS +// ----------------- +// Exact selectors below required to override .table-striped + +.table tbody tr { + &.success > td { + background-color: @successBackground; + } + &.error > td { + background-color: @errorBackground; + } + &.warning > td { + background-color: @warningBackground; + } + &.info > td { + background-color: @infoBackground; + } +} + +// Hover states for .table-hover +.table-hover tbody tr { + &.success:hover > td { + background-color: darken(@successBackground, 5%); + } + &.error:hover > td { + background-color: darken(@errorBackground, 5%); + } + &.warning:hover > td { + background-color: darken(@warningBackground, 5%); + } + &.info:hover > td { + background-color: darken(@infoBackground, 5%); + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/thumbnails.less b/openid-connect-server-webapp/src/main/webapp/less/thumbnails.less new file mode 100644 index 0000000000..4fd07d2533 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/thumbnails.less @@ -0,0 +1,53 @@ +// +// Thumbnails +// -------------------------------------------------- + + +// Note: `.thumbnails` and `.thumbnails > li` are overriden in responsive files + +// Make wrapper ul behave like the grid +.thumbnails { + margin-left: -@gridGutterWidth; + list-style: none; + .clearfix(); +} +// Fluid rows have no left margin +.row-fluid .thumbnails { + margin-left: 0; +} + +// Float li to make thumbnails appear in a row +.thumbnails > li { + float: left; // Explicity set the float since we don't require .span* classes + margin-bottom: @baseLineHeight; + margin-left: @gridGutterWidth; +} + +// The actual thumbnail (can be `a` or `div`) +.thumbnail { + display: block; + padding: 4px; + line-height: @baseLineHeight; + border: 1px solid #ddd; + .border-radius(@baseBorderRadius); + .box-shadow(0 1px 3px rgba(0,0,0,.055)); + .transition(all .2s ease-in-out); +} +// Add a hover/focus state for linked versions only +a.thumbnail:hover, +a.thumbnail:focus { + border-color: @linkColor; + .box-shadow(0 1px 4px rgba(0,105,214,.25)); +} + +// Images and captions +.thumbnail > img { + display: block; + max-width: 100%; + margin-left: auto; + margin-right: auto; +} +.thumbnail .caption { + padding: 9px; + color: @gray; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/tooltip.less b/openid-connect-server-webapp/src/main/webapp/less/tooltip.less new file mode 100644 index 0000000000..83d5f2bd76 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/tooltip.less @@ -0,0 +1,70 @@ +// +// Tooltips +// -------------------------------------------------- + + +// Base class +.tooltip { + position: absolute; + z-index: @zindexTooltip; + display: block; + visibility: visible; + font-size: 11px; + line-height: 1.4; + .opacity(0); + &.in { .opacity(80); } + &.top { margin-top: -3px; padding: 5px 0; } + &.right { margin-left: 3px; padding: 0 5px; } + &.bottom { margin-top: 3px; padding: 5px 0; } + &.left { margin-left: -3px; padding: 0 5px; } +} + +// Wrapper for the tooltip content +.tooltip-inner { + max-width: 200px; + padding: 8px; + color: @tooltipColor; + text-align: center; + text-decoration: none; + background-color: @tooltipBackground; + .border-radius(@baseBorderRadius); +} + +// Arrows +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip { + &.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth @tooltipArrowWidth 0; + border-top-color: @tooltipArrowColor; + } + &.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth @tooltipArrowWidth @tooltipArrowWidth 0; + border-right-color: @tooltipArrowColor; + } + &.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -@tooltipArrowWidth; + border-width: @tooltipArrowWidth 0 @tooltipArrowWidth @tooltipArrowWidth; + border-left-color: @tooltipArrowColor; + } + &.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -@tooltipArrowWidth; + border-width: 0 @tooltipArrowWidth @tooltipArrowWidth; + border-bottom-color: @tooltipArrowColor; + } +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/type.less b/openid-connect-server-webapp/src/main/webapp/less/type.less new file mode 100644 index 0000000000..6a472db497 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/type.less @@ -0,0 +1,247 @@ +// +// Typography +// -------------------------------------------------- + + +// Body text +// ------------------------- + +p { + margin: 0 0 @baseLineHeight / 2; +} +.lead { + margin-bottom: @baseLineHeight; + font-size: @baseFontSize * 1.5; + font-weight: 200; + line-height: @baseLineHeight * 1.5; +} + + +// Emphasis & misc +// ------------------------- + +// Ex: 14px base font * 85% = about 12px +small { font-size: 85%; } + +strong { font-weight: bold; } +em { font-style: italic; } +cite { font-style: normal; } + +// Utility classes +.muted { color: @grayLight; } +a.muted:hover, +a.muted:focus { color: darken(@grayLight, 10%); } + +.text-warning { color: @warningText; } +a.text-warning:hover, +a.text-warning:focus { color: darken(@warningText, 10%); } + +.text-error { color: @errorText; } +a.text-error:hover, +a.text-error:focus { color: darken(@errorText, 10%); } + +.text-info { color: @infoText; } +a.text-info:hover, +a.text-info:focus { color: darken(@infoText, 10%); } + +.text-success { color: @successText; } +a.text-success:hover, +a.text-success:focus { color: darken(@successText, 10%); } + +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6 { + margin: (@baseLineHeight / 2) 0; + font-family: @headingsFontFamily; + font-weight: @headingsFontWeight; + line-height: @baseLineHeight; + color: @headingsColor; + text-rendering: optimizelegibility; // Fix the character spacing for headings + small { + font-weight: normal; + line-height: 1; + color: @grayLight; + } +} + +h1, +h2, +h3 { line-height: @baseLineHeight * 2; } + +h1 { font-size: @baseFontSize * 2.75; } // ~38px +h2 { font-size: @baseFontSize * 2.25; } // ~32px +h3 { font-size: @baseFontSize * 1.75; } // ~24px +h4 { font-size: @baseFontSize * 1.25; } // ~18px +h5 { font-size: @baseFontSize; } +h6 { font-size: @baseFontSize * 0.85; } // ~12px + +h1 small { font-size: @baseFontSize * 1.75; } // ~24px +h2 small { font-size: @baseFontSize * 1.25; } // ~18px +h3 small { font-size: @baseFontSize; } +h4 small { font-size: @baseFontSize; } + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: (@baseLineHeight / 2) - 1; + margin: @baseLineHeight 0 (@baseLineHeight * 1.5); + border-bottom: 1px solid @grayLighter; +} + + + +// Lists +// -------------------------------------------------- + +// Unordered and Ordered lists +ul, ol { + padding: 0; + margin: 0 0 @baseLineHeight / 2 25px; +} +ul ul, +ul ol, +ol ol, +ol ul { + margin-bottom: 0; +} +li { + line-height: @baseLineHeight; +} + +// Remove default list styles +ul.unstyled, +ol.unstyled { + margin-left: 0; + list-style: none; +} + +// Single-line list items +ul.inline, +ol.inline { + margin-left: 0; + list-style: none; + > li { + display: inline-block; + .ie7-inline-block(); + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-bottom: @baseLineHeight; +} +dt, +dd { + line-height: @baseLineHeight; +} +dt { + font-weight: bold; +} +dd { + margin-left: @baseLineHeight / 2; +} +// Horizontal layout (like forms) +.dl-horizontal { + .clearfix(); // Ensure dl clears floats if empty dd elements present + dt { + float: left; + width: @horizontalComponentOffset - 20; + clear: left; + text-align: right; + .text-overflow(); + } + dd { + margin-left: @horizontalComponentOffset; + } +} + +// MISC +// ---- + +// Horizontal rules +hr { + margin: @baseLineHeight 0; + border: 0; + border-top: 1px solid @hrBorder; + border-bottom: 1px solid @white; +} + +// Abbreviations and acronyms +abbr[title], +// Added data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted @grayLight; +} +abbr.initialism { + font-size: 90%; + text-transform: uppercase; +} + +// Blockquotes +blockquote { + padding: 0 0 0 15px; + margin: 0 0 @baseLineHeight; + border-left: 5px solid @grayLighter; + p { + margin-bottom: 0; + font-size: @baseFontSize * 1.25; + font-weight: 300; + line-height: 1.25; + } + small { + display: block; + line-height: @baseLineHeight; + color: @grayLight; + &:before { + content: '\2014 \00A0'; + } + } + + // Float right with text-align: right + &.pull-right { + float: right; + padding-right: 15px; + padding-left: 0; + border-right: 5px solid @grayLighter; + border-left: 0; + p, + small { + text-align: right; + } + small { + &:before { + content: ''; + } + &:after { + content: '\00A0 \2014'; + } + } + } +} + +// Quotes +q:before, +q:after, +blockquote:before, +blockquote:after { + content: ""; +} + +// Addresses +address { + display: block; + margin-bottom: @baseLineHeight; + font-style: normal; + line-height: @baseLineHeight; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/utilities.less b/openid-connect-server-webapp/src/main/webapp/less/utilities.less new file mode 100644 index 0000000000..314b4ffdb4 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/utilities.less @@ -0,0 +1,30 @@ +// +// Utility classes +// -------------------------------------------------- + + +// Quick floats +.pull-right { + float: right; +} +.pull-left { + float: left; +} + +// Toggling content +.hide { + display: none; +} +.show { + display: block; +} + +// Visibility +.invisible { + visibility: hidden; +} + +// For Affix plugin +.affix { + position: fixed; +} diff --git a/openid-connect-server-webapp/src/main/webapp/less/variables.less b/openid-connect-server-webapp/src/main/webapp/less/variables.less new file mode 100644 index 0000000000..31c131b1e2 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/variables.less @@ -0,0 +1,301 @@ +// +// Variables +// -------------------------------------------------- + + +// Global values +// -------------------------------------------------- + + +// Grays +// ------------------------- +@black: #000; +@grayDarker: #222; +@grayDark: #333; +@gray: #555; +@grayLight: #999; +@grayLighter: #eee; +@white: #fff; + + +// Accent colors +// ------------------------- +@blue: #049cdb; +@blueDark: #0064cd; +@green: #46a546; +@red: #9d261d; +@yellow: #ffc40d; +@orange: #f89406; +@pink: #c3325f; +@purple: #7a43b6; + + +// Scaffolding +// ------------------------- +@bodyBackground: @white; +@textColor: @grayDark; + + +// Links +// ------------------------- +@linkColor: #08c; +@linkColorHover: darken(@linkColor, 15%); + + +// Typography +// ------------------------- +@sansFontFamily: "Helvetica Neue", Helvetica, Arial, sans-serif; +@serifFontFamily: Georgia, "Times New Roman", Times, serif; +@monoFontFamily: Monaco, Menlo, Consolas, "Courier New", monospace; + +@baseFontSize: 14px; +@baseFontFamily: @sansFontFamily; +@baseLineHeight: 20px; +@altFontFamily: @serifFontFamily; + +@headingsFontFamily: inherit; // empty to use BS default, @baseFontFamily +@headingsFontWeight: bold; // instead of browser default, bold +@headingsColor: inherit; // empty to use BS default, @textColor + + +// Component sizing +// ------------------------- +// Based on 14px font-size and 20px line-height + +@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeSmall: @baseFontSize * 0.85; // ~12px +@fontSizeMini: @baseFontSize * 0.75; // ~11px + +@paddingLarge: 11px 19px; // 44px +@paddingSmall: 2px 10px; // 26px +@paddingMini: 0 6px; // 22px + +@baseBorderRadius: 4px; +@borderRadiusLarge: 6px; +@borderRadiusSmall: 3px; + + +// Tables +// ------------------------- +@tableBackground: transparent; // overall background-color +@tableBackgroundAccent: #f9f9f9; // for striping +@tableBackgroundHover: #f5f5f5; // for hover +@tableBorder: #ddd; // table and cell border + +// Buttons +// ------------------------- +@btnBackground: @white; +@btnBackgroundHighlight: darken(@white, 10%); +@btnBorder: #ccc; + +@btnPrimaryBackground: @linkColor; +@btnPrimaryBackgroundHighlight: spin(@btnPrimaryBackground, 20%); + +@btnInfoBackground: #5bc0de; +@btnInfoBackgroundHighlight: #2f96b4; + +@btnSuccessBackground: #62c462; +@btnSuccessBackgroundHighlight: #51a351; + +@btnWarningBackground: lighten(@orange, 15%); +@btnWarningBackgroundHighlight: @orange; + +@btnDangerBackground: #ee5f5b; +@btnDangerBackgroundHighlight: #bd362f; + +@btnInverseBackground: #444; +@btnInverseBackgroundHighlight: @grayDarker; + + +// Forms +// ------------------------- +@inputBackground: @white; +@inputBorder: #ccc; +@inputBorderRadius: @baseBorderRadius; +@inputDisabledBackground: @grayLighter; +@formActionsBackground: #f5f5f5; +@inputHeight: @baseLineHeight + 10px; // base line-height + 8px vertical padding + 2px top/bottom border + + +// Dropdowns +// ------------------------- +@dropdownBackground: @white; +@dropdownBorder: rgba(0,0,0,.2); +@dropdownDividerTop: #e5e5e5; +@dropdownDividerBottom: @white; + +@dropdownLinkColor: @grayDark; +@dropdownLinkColorHover: @white; +@dropdownLinkColorActive: @white; + +@dropdownLinkBackgroundActive: @linkColor; +@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +@zindexDropdown: 1000; +@zindexPopover: 1010; +@zindexTooltip: 1030; +@zindexFixedNavbar: 1030; +@zindexModalBackdrop: 1040; +@zindexModal: 1050; + + +// Sprite icons path +// ------------------------- +@iconSpritePath: "../img/glyphicons-halflings.png"; +@iconWhiteSpritePath: "../img/glyphicons-halflings-white.png"; + + +// Input placeholder text color +// ------------------------- +@placeholderText: @grayLight; + + +// Hr border color +// ------------------------- +@hrBorder: @grayLighter; + + +// Horizontal forms & lists +// ------------------------- +@horizontalComponentOffset: 180px; + + +// Wells +// ------------------------- +@wellBackground: #f5f5f5; + + +// Navbar +// ------------------------- +@navbarCollapseWidth: 979px; +@navbarCollapseDesktopWidth: @navbarCollapseWidth + 1; + +@navbarHeight: 40px; +@navbarBackgroundHighlight: #ffffff; +@navbarBackground: darken(@navbarBackgroundHighlight, 5%); +@navbarBorder: darken(@navbarBackground, 12%); + +@navbarText: #777; +@navbarLinkColor: #777; +@navbarLinkColorHover: @grayDark; +@navbarLinkColorActive: @gray; +@navbarLinkBackgroundHover: transparent; +@navbarLinkBackgroundActive: darken(@navbarBackground, 5%); + +@navbarBrandColor: @navbarLinkColor; + +// Inverted navbar +@navbarInverseBackground: #111111; +@navbarInverseBackgroundHighlight: #222222; +@navbarInverseBorder: #252525; + +@navbarInverseText: @grayLight; +@navbarInverseLinkColor: @grayLight; +@navbarInverseLinkColorHover: @white; +@navbarInverseLinkColorActive: @navbarInverseLinkColorHover; +@navbarInverseLinkBackgroundHover: transparent; +@navbarInverseLinkBackgroundActive: @navbarInverseBackground; + +@navbarInverseSearchBackground: lighten(@navbarInverseBackground, 25%); +@navbarInverseSearchBackgroundFocus: @white; +@navbarInverseSearchBorder: @navbarInverseBackground; +@navbarInverseSearchPlaceholderColor: #ccc; + +@navbarInverseBrandColor: @navbarInverseLinkColor; + + +// Pagination +// ------------------------- +@paginationBackground: #fff; +@paginationBorder: #ddd; +@paginationActiveBackground: #f5f5f5; + + +// Hero unit +// ------------------------- +@heroUnitBackground: @grayLighter; +@heroUnitHeadingColor: inherit; +@heroUnitLeadColor: inherit; + + +// Form states and alerts +// ------------------------- +@warningText: #c09853; +@warningBackground: #fcf8e3; +@warningBorder: darken(spin(@warningBackground, -10), 3%); + +@errorText: #b94a48; +@errorBackground: #f2dede; +@errorBorder: darken(spin(@errorBackground, -10), 3%); + +@successText: #468847; +@successBackground: #dff0d8; +@successBorder: darken(spin(@successBackground, -10), 5%); + +@infoText: #3a87ad; +@infoBackground: #d9edf7; +@infoBorder: darken(spin(@infoBackground, -10), 7%); + + +// Tooltips and popovers +// ------------------------- +@tooltipColor: #fff; +@tooltipBackground: #000; +@tooltipArrowWidth: 5px; +@tooltipArrowColor: @tooltipBackground; + +@popoverBackground: #fff; +@popoverArrowWidth: 10px; +@popoverArrowColor: #fff; +@popoverTitleBackground: darken(@popoverBackground, 3%); + +// Special enhancement for popovers +@popoverArrowOuterWidth: @popoverArrowWidth + 1; +@popoverArrowOuterColor: rgba(0,0,0,.25); + + + +// GRID +// -------------------------------------------------- + + +// Default 940px grid +// ------------------------- +@gridColumns: 12; +@gridColumnWidth: 60px; +@gridGutterWidth: 20px; +@gridRowWidth: (@gridColumns * @gridColumnWidth) + (@gridGutterWidth * (@gridColumns - 1)); + +// 1200px min +@gridColumnWidth1200: 70px; +@gridGutterWidth1200: 30px; +@gridRowWidth1200: (@gridColumns * @gridColumnWidth1200) + (@gridGutterWidth1200 * (@gridColumns - 1)); + +// 768px-979px +@gridColumnWidth768: 42px; +@gridGutterWidth768: 20px; +@gridRowWidth768: (@gridColumns * @gridColumnWidth768) + (@gridGutterWidth768 * (@gridColumns - 1)); + + +// Fluid grid +// ------------------------- +@fluidGridColumnWidth: percentage(@gridColumnWidth/@gridRowWidth); +@fluidGridGutterWidth: percentage(@gridGutterWidth/@gridRowWidth); + +// 1200px min +@fluidGridColumnWidth1200: percentage(@gridColumnWidth1200/@gridRowWidth1200); +@fluidGridGutterWidth1200: percentage(@gridGutterWidth1200/@gridRowWidth1200); + +// 768px-979px +@fluidGridColumnWidth768: percentage(@gridColumnWidth768/@gridRowWidth768); +@fluidGridGutterWidth768: percentage(@gridGutterWidth768/@gridRowWidth768); diff --git a/openid-connect-server-webapp/src/main/webapp/less/wells.less b/openid-connect-server-webapp/src/main/webapp/less/wells.less new file mode 100644 index 0000000000..84a744b1c5 --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/less/wells.less @@ -0,0 +1,29 @@ +// +// Wells +// -------------------------------------------------- + + +// Base class +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: @wellBackground; + border: 1px solid darken(@wellBackground, 7%); + .border-radius(@baseBorderRadius); + .box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); + blockquote { + border-color: #ddd; + border-color: rgba(0,0,0,.15); + } +} + +// Sizes +.well-large { + padding: 24px; + .border-radius(@borderRadiusLarge); +} +.well-small { + padding: 9px; + .border-radius(@borderRadiusSmall); +} diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap-responsive.css b/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap-responsive.css deleted file mode 100644 index a3352d774c..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap-responsive.css +++ /dev/null @@ -1,1092 +0,0 @@ -/*! - * Bootstrap Responsive v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - -@-ms-viewport { - width: device-width; -} - -.clearfix { - *zoom: 1; -} - -.clearfix:before, -.clearfix:after { - display: table; - line-height: 0; - content: ""; -} - -.clearfix:after { - clear: both; -} - -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.input-block-level { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.hidden { - display: none; - visibility: hidden; -} - -.visible-phone { - display: none !important; -} - -.visible-tablet { - display: none !important; -} - -.hidden-desktop { - display: none !important; -} - -.visible-desktop { - display: inherit !important; -} - -@media (min-width: 768px) and (max-width: 979px) { - .hidden-desktop { - display: inherit !important; - } - .visible-desktop { - display: none !important ; - } - .visible-tablet { - display: inherit !important; - } - .hidden-tablet { - display: none !important; - } -} - -@media (max-width: 767px) { - .hidden-desktop { - display: inherit !important; - } - .visible-desktop { - display: none !important; - } - .visible-phone { - display: inherit !important; - } - .hidden-phone { - display: none !important; - } -} - -@media (min-width: 1200px) { - .row { - margin-left: -30px; - *zoom: 1; - } - .row:before, - .row:after { - display: table; - line-height: 0; - content: ""; - } - .row:after { - clear: both; - } - [class*="span"] { - float: left; - min-height: 1px; - margin-left: 30px; - } - .container, - .navbar-static-top .container, - .navbar-fixed-top .container, - .navbar-fixed-bottom .container { - width: 1170px; - } - .span12 { - width: 1170px; - } - .span11 { - width: 1070px; - } - .span10 { - width: 970px; - } - .span9 { - width: 870px; - } - .span8 { - width: 770px; - } - .span7 { - width: 670px; - } - .span6 { - width: 570px; - } - .span5 { - width: 470px; - } - .span4 { - width: 370px; - } - .span3 { - width: 270px; - } - .span2 { - width: 170px; - } - .span1 { - width: 70px; - } - .offset12 { - margin-left: 1230px; - } - .offset11 { - margin-left: 1130px; - } - .offset10 { - margin-left: 1030px; - } - .offset9 { - margin-left: 930px; - } - .offset8 { - margin-left: 830px; - } - .offset7 { - margin-left: 730px; - } - .offset6 { - margin-left: 630px; - } - .offset5 { - margin-left: 530px; - } - .offset4 { - margin-left: 430px; - } - .offset3 { - margin-left: 330px; - } - .offset2 { - margin-left: 230px; - } - .offset1 { - margin-left: 130px; - } - .row-fluid { - width: 100%; - *zoom: 1; - } - .row-fluid:before, - .row-fluid:after { - display: table; - line-height: 0; - content: ""; - } - .row-fluid:after { - clear: both; - } - .row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.564102564102564%; - *margin-left: 2.5109110747408616%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .row-fluid [class*="span"]:first-child { - margin-left: 0; - } - .row-fluid .controls-row [class*="span"] + [class*="span"] { - margin-left: 2.564102564102564%; - } - .row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; - } - .row-fluid .span11 { - width: 91.45299145299145%; - *width: 91.39979996362975%; - } - .row-fluid .span10 { - width: 82.90598290598291%; - *width: 82.8527914166212%; - } - .row-fluid .span9 { - width: 74.35897435897436%; - *width: 74.30578286961266%; - } - .row-fluid .span8 { - width: 65.81196581196582%; - *width: 65.75877432260411%; - } - .row-fluid .span7 { - width: 57.26495726495726%; - *width: 57.21176577559556%; - } - .row-fluid .span6 { - width: 48.717948717948715%; - *width: 48.664757228587014%; - } - .row-fluid .span5 { - width: 40.17094017094017%; - *width: 40.11774868157847%; - } - .row-fluid .span4 { - width: 31.623931623931625%; - *width: 31.570740134569924%; - } - .row-fluid .span3 { - width: 23.076923076923077%; - *width: 23.023731587561375%; - } - .row-fluid .span2 { - width: 14.52991452991453%; - *width: 14.476723040552828%; - } - .row-fluid .span1 { - width: 5.982905982905983%; - *width: 5.929714493544281%; - } - .row-fluid .offset12 { - margin-left: 105.12820512820512%; - *margin-left: 105.02182214948171%; - } - .row-fluid .offset12:first-child { - margin-left: 102.56410256410257%; - *margin-left: 102.45771958537915%; - } - .row-fluid .offset11 { - margin-left: 96.58119658119658%; - *margin-left: 96.47481360247316%; - } - .row-fluid .offset11:first-child { - margin-left: 94.01709401709402%; - *margin-left: 93.91071103837061%; - } - .row-fluid .offset10 { - margin-left: 88.03418803418803%; - *margin-left: 87.92780505546462%; - } - .row-fluid .offset10:first-child { - margin-left: 85.47008547008548%; - *margin-left: 85.36370249136206%; - } - .row-fluid .offset9 { - margin-left: 79.48717948717949%; - *margin-left: 79.38079650845607%; - } - .row-fluid .offset9:first-child { - margin-left: 76.92307692307693%; - *margin-left: 76.81669394435352%; - } - .row-fluid .offset8 { - margin-left: 70.94017094017094%; - *margin-left: 70.83378796144753%; - } - .row-fluid .offset8:first-child { - margin-left: 68.37606837606839%; - *margin-left: 68.26968539734497%; - } - .row-fluid .offset7 { - margin-left: 62.393162393162385%; - *margin-left: 62.28677941443899%; - } - .row-fluid .offset7:first-child { - margin-left: 59.82905982905982%; - *margin-left: 59.72267685033642%; - } - .row-fluid .offset6 { - margin-left: 53.84615384615384%; - *margin-left: 53.739770867430444%; - } - .row-fluid .offset6:first-child { - margin-left: 51.28205128205128%; - *margin-left: 51.175668303327875%; - } - .row-fluid .offset5 { - margin-left: 45.299145299145295%; - *margin-left: 45.1927623204219%; - } - .row-fluid .offset5:first-child { - margin-left: 42.73504273504273%; - *margin-left: 42.62865975631933%; - } - .row-fluid .offset4 { - margin-left: 36.75213675213675%; - *margin-left: 36.645753773413354%; - } - .row-fluid .offset4:first-child { - margin-left: 34.18803418803419%; - *margin-left: 34.081651209310785%; - } - .row-fluid .offset3 { - margin-left: 28.205128205128204%; - *margin-left: 28.0987452264048%; - } - .row-fluid .offset3:first-child { - margin-left: 25.641025641025642%; - *margin-left: 25.53464266230224%; - } - .row-fluid .offset2 { - margin-left: 19.65811965811966%; - *margin-left: 19.551736679396257%; - } - .row-fluid .offset2:first-child { - margin-left: 17.094017094017094%; - *margin-left: 16.98763411529369%; - } - .row-fluid .offset1 { - margin-left: 11.11111111111111%; - *margin-left: 11.004728132387708%; - } - .row-fluid .offset1:first-child { - margin-left: 8.547008547008547%; - *margin-left: 8.440625568285142%; - } - input, - textarea, - .uneditable-input { - margin-left: 0; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 30px; - } - input.span12, - textarea.span12, - .uneditable-input.span12 { - width: 1156px; - } - input.span11, - textarea.span11, - .uneditable-input.span11 { - width: 1056px; - } - input.span10, - textarea.span10, - .uneditable-input.span10 { - width: 956px; - } - input.span9, - textarea.span9, - .uneditable-input.span9 { - width: 856px; - } - input.span8, - textarea.span8, - .uneditable-input.span8 { - width: 756px; - } - input.span7, - textarea.span7, - .uneditable-input.span7 { - width: 656px; - } - input.span6, - textarea.span6, - .uneditable-input.span6 { - width: 556px; - } - input.span5, - textarea.span5, - .uneditable-input.span5 { - width: 456px; - } - input.span4, - textarea.span4, - .uneditable-input.span4 { - width: 356px; - } - input.span3, - textarea.span3, - .uneditable-input.span3 { - width: 256px; - } - input.span2, - textarea.span2, - .uneditable-input.span2 { - width: 156px; - } - input.span1, - textarea.span1, - .uneditable-input.span1 { - width: 56px; - } - .thumbnails { - margin-left: -30px; - } - .thumbnails > li { - margin-left: 30px; - } - .row-fluid .thumbnails { - margin-left: 0; - } -} - -@media (min-width: 768px) and (max-width: 979px) { - .row { - margin-left: -20px; - *zoom: 1; - } - .row:before, - .row:after { - display: table; - line-height: 0; - content: ""; - } - .row:after { - clear: both; - } - [class*="span"] { - float: left; - min-height: 1px; - margin-left: 20px; - } - .container, - .navbar-static-top .container, - .navbar-fixed-top .container, - .navbar-fixed-bottom .container { - width: 724px; - } - .span12 { - width: 724px; - } - .span11 { - width: 662px; - } - .span10 { - width: 600px; - } - .span9 { - width: 538px; - } - .span8 { - width: 476px; - } - .span7 { - width: 414px; - } - .span6 { - width: 352px; - } - .span5 { - width: 290px; - } - .span4 { - width: 228px; - } - .span3 { - width: 166px; - } - .span2 { - width: 104px; - } - .span1 { - width: 42px; - } - .offset12 { - margin-left: 764px; - } - .offset11 { - margin-left: 702px; - } - .offset10 { - margin-left: 640px; - } - .offset9 { - margin-left: 578px; - } - .offset8 { - margin-left: 516px; - } - .offset7 { - margin-left: 454px; - } - .offset6 { - margin-left: 392px; - } - .offset5 { - margin-left: 330px; - } - .offset4 { - margin-left: 268px; - } - .offset3 { - margin-left: 206px; - } - .offset2 { - margin-left: 144px; - } - .offset1 { - margin-left: 82px; - } - .row-fluid { - width: 100%; - *zoom: 1; - } - .row-fluid:before, - .row-fluid:after { - display: table; - line-height: 0; - content: ""; - } - .row-fluid:after { - clear: both; - } - .row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.7624309392265194%; - *margin-left: 2.709239449864817%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .row-fluid [class*="span"]:first-child { - margin-left: 0; - } - .row-fluid .controls-row [class*="span"] + [class*="span"] { - margin-left: 2.7624309392265194%; - } - .row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; - } - .row-fluid .span11 { - width: 91.43646408839778%; - *width: 91.38327259903608%; - } - .row-fluid .span10 { - width: 82.87292817679558%; - *width: 82.81973668743387%; - } - .row-fluid .span9 { - width: 74.30939226519337%; - *width: 74.25620077583166%; - } - .row-fluid .span8 { - width: 65.74585635359117%; - *width: 65.69266486422946%; - } - .row-fluid .span7 { - width: 57.18232044198895%; - *width: 57.12912895262725%; - } - .row-fluid .span6 { - width: 48.61878453038674%; - *width: 48.56559304102504%; - } - .row-fluid .span5 { - width: 40.05524861878453%; - *width: 40.00205712942283%; - } - .row-fluid .span4 { - width: 31.491712707182323%; - *width: 31.43852121782062%; - } - .row-fluid .span3 { - width: 22.92817679558011%; - *width: 22.87498530621841%; - } - .row-fluid .span2 { - width: 14.3646408839779%; - *width: 14.311449394616199%; - } - .row-fluid .span1 { - width: 5.801104972375691%; - *width: 5.747913483013988%; - } - .row-fluid .offset12 { - margin-left: 105.52486187845304%; - *margin-left: 105.41847889972962%; - } - .row-fluid .offset12:first-child { - margin-left: 102.76243093922652%; - *margin-left: 102.6560479605031%; - } - .row-fluid .offset11 { - margin-left: 96.96132596685082%; - *margin-left: 96.8549429881274%; - } - .row-fluid .offset11:first-child { - margin-left: 94.1988950276243%; - *margin-left: 94.09251204890089%; - } - .row-fluid .offset10 { - margin-left: 88.39779005524862%; - *margin-left: 88.2914070765252%; - } - .row-fluid .offset10:first-child { - margin-left: 85.6353591160221%; - *margin-left: 85.52897613729868%; - } - .row-fluid .offset9 { - margin-left: 79.8342541436464%; - *margin-left: 79.72787116492299%; - } - .row-fluid .offset9:first-child { - margin-left: 77.07182320441989%; - *margin-left: 76.96544022569647%; - } - .row-fluid .offset8 { - margin-left: 71.2707182320442%; - *margin-left: 71.16433525332079%; - } - .row-fluid .offset8:first-child { - margin-left: 68.50828729281768%; - *margin-left: 68.40190431409427%; - } - .row-fluid .offset7 { - margin-left: 62.70718232044199%; - *margin-left: 62.600799341718584%; - } - .row-fluid .offset7:first-child { - margin-left: 59.94475138121547%; - *margin-left: 59.838368402492065%; - } - .row-fluid .offset6 { - margin-left: 54.14364640883978%; - *margin-left: 54.037263430116376%; - } - .row-fluid .offset6:first-child { - margin-left: 51.38121546961326%; - *margin-left: 51.27483249088986%; - } - .row-fluid .offset5 { - margin-left: 45.58011049723757%; - *margin-left: 45.47372751851417%; - } - .row-fluid .offset5:first-child { - margin-left: 42.81767955801105%; - *margin-left: 42.71129657928765%; - } - .row-fluid .offset4 { - margin-left: 37.01657458563536%; - *margin-left: 36.91019160691196%; - } - .row-fluid .offset4:first-child { - margin-left: 34.25414364640884%; - *margin-left: 34.14776066768544%; - } - .row-fluid .offset3 { - margin-left: 28.45303867403315%; - *margin-left: 28.346655695309746%; - } - .row-fluid .offset3:first-child { - margin-left: 25.69060773480663%; - *margin-left: 25.584224756083227%; - } - .row-fluid .offset2 { - margin-left: 19.88950276243094%; - *margin-left: 19.783119783707537%; - } - .row-fluid .offset2:first-child { - margin-left: 17.12707182320442%; - *margin-left: 17.02068884448102%; - } - .row-fluid .offset1 { - margin-left: 11.32596685082873%; - *margin-left: 11.219583872105325%; - } - .row-fluid .offset1:first-child { - margin-left: 8.56353591160221%; - *margin-left: 8.457152932878806%; - } - input, - textarea, - .uneditable-input { - margin-left: 0; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 20px; - } - input.span12, - textarea.span12, - .uneditable-input.span12 { - width: 710px; - } - input.span11, - textarea.span11, - .uneditable-input.span11 { - width: 648px; - } - input.span10, - textarea.span10, - .uneditable-input.span10 { - width: 586px; - } - input.span9, - textarea.span9, - .uneditable-input.span9 { - width: 524px; - } - input.span8, - textarea.span8, - .uneditable-input.span8 { - width: 462px; - } - input.span7, - textarea.span7, - .uneditable-input.span7 { - width: 400px; - } - input.span6, - textarea.span6, - .uneditable-input.span6 { - width: 338px; - } - input.span5, - textarea.span5, - .uneditable-input.span5 { - width: 276px; - } - input.span4, - textarea.span4, - .uneditable-input.span4 { - width: 214px; - } - input.span3, - textarea.span3, - .uneditable-input.span3 { - width: 152px; - } - input.span2, - textarea.span2, - .uneditable-input.span2 { - width: 90px; - } - input.span1, - textarea.span1, - .uneditable-input.span1 { - width: 28px; - } -} - -@media (max-width: 767px) { - body { - padding-right: 20px; - padding-left: 20px; - } - .navbar-fixed-top, - .navbar-fixed-bottom, - .navbar-static-top { - margin-right: -20px; - margin-left: -20px; - } - .container-fluid { - padding: 0; - } - .dl-horizontal dt { - float: none; - width: auto; - clear: none; - text-align: left; - } - .dl-horizontal dd { - margin-left: 0; - } - .container { - width: auto; - } - .row-fluid { - width: 100%; - } - .row, - .thumbnails { - margin-left: 0; - } - .thumbnails > li { - float: none; - margin-left: 0; - } - [class*="span"], - .uneditable-input[class*="span"], - .row-fluid [class*="span"] { - display: block; - float: none; - width: 100%; - margin-left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .span12, - .row-fluid .span12 { - width: 100%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .row-fluid [class*="offset"]:first-child { - margin-left: 0; - } - .input-large, - .input-xlarge, - .input-xxlarge, - input[class*="span"], - select[class*="span"], - textarea[class*="span"], - .uneditable-input { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - } - .input-prepend input, - .input-append input, - .input-prepend input[class*="span"], - .input-append input[class*="span"] { - display: inline-block; - width: auto; - } - .controls-row [class*="span"] + [class*="span"] { - margin-left: 0; - } - .modal { - position: fixed; - top: 20px; - right: 20px; - left: 20px; - width: auto; - margin: 0; - } - .modal.fade { - top: -100px; - } - .modal.fade.in { - top: 20px; - } -} - -@media (max-width: 480px) { - .nav-collapse { - -webkit-transform: translate3d(0, 0, 0); - } - .page-header h1 small { - display: block; - line-height: 20px; - } - input[type="checkbox"], - input[type="radio"] { - border: 1px solid #ccc; - } - .form-horizontal .control-label { - float: none; - width: auto; - padding-top: 0; - text-align: left; - } - .form-horizontal .controls { - margin-left: 0; - } - .form-horizontal .control-list { - padding-top: 0; - } - .form-horizontal .form-actions { - padding-right: 10px; - padding-left: 10px; - } - .media .pull-left, - .media .pull-right { - display: block; - float: none; - margin-bottom: 10px; - } - .media-object { - margin-right: 0; - margin-left: 0; - } - .modal { - top: 10px; - right: 10px; - left: 10px; - } - .modal-header .close { - padding: 10px; - margin: -10px; - } - .carousel-caption { - position: static; - } -} - -@media (max-width: 979px) { - body { - padding-top: 0; - } - .navbar-fixed-top, - .navbar-fixed-bottom { - position: static; - } - .navbar-fixed-top { - margin-bottom: 20px; - } - .navbar-fixed-bottom { - margin-top: 20px; - } - .navbar-fixed-top .navbar-inner, - .navbar-fixed-bottom .navbar-inner { - padding: 5px; - } - .navbar .container { - width: auto; - padding: 0; - } - .navbar .brand { - padding-right: 10px; - padding-left: 10px; - margin: 0 0 0 -5px; - } - .nav-collapse { - clear: both; - } - .nav-collapse .nav { - float: none; - margin: 0 0 10px; - } - .nav-collapse .nav > li { - float: none; - } - .nav-collapse .nav > li > a { - margin-bottom: 2px; - } - .nav-collapse .nav > .divider-vertical { - display: none; - } - .nav-collapse .nav .nav-header { - color: #777777; - text-shadow: none; - } - .nav-collapse .nav > li > a, - .nav-collapse .dropdown-menu a { - padding: 9px 15px; - font-weight: bold; - color: #777777; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - } - .nav-collapse .btn { - padding: 4px 10px 4px; - font-weight: normal; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - } - .nav-collapse .dropdown-menu li + li a { - margin-bottom: 2px; - } - .nav-collapse .nav > li > a:hover, - .nav-collapse .dropdown-menu a:hover { - background-color: #f2f2f2; - } - .navbar-inverse .nav-collapse .nav > li > a, - .navbar-inverse .nav-collapse .dropdown-menu a { - color: #999999; - } - .navbar-inverse .nav-collapse .nav > li > a:hover, - .navbar-inverse .nav-collapse .dropdown-menu a:hover { - background-color: #111111; - } - .nav-collapse.in .btn-group { - padding: 0; - margin-top: 5px; - } - .nav-collapse .dropdown-menu { - position: static; - top: auto; - left: auto; - display: none; - float: none; - max-width: none; - padding: 0; - margin: 0 15px; - background-color: transparent; - border: none; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; - } - .nav-collapse .open > .dropdown-menu { - display: block; - } - .nav-collapse .dropdown-menu:before, - .nav-collapse .dropdown-menu:after { - display: none; - } - .nav-collapse .dropdown-menu .divider { - display: none; - } - .nav-collapse .nav > li > .dropdown-menu:before, - .nav-collapse .nav > li > .dropdown-menu:after { - display: none; - } - .nav-collapse .navbar-form, - .nav-collapse .navbar-search { - float: none; - padding: 10px 15px; - margin: 10px 0; - border-top: 1px solid #f2f2f2; - border-bottom: 1px solid #f2f2f2; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); - } - .navbar-inverse .nav-collapse .navbar-form, - .navbar-inverse .nav-collapse .navbar-search { - border-top-color: #111111; - border-bottom-color: #111111; - } - .navbar .nav-collapse .nav.pull-right { - float: none; - margin-left: 0; - } - .nav-collapse, - .nav-collapse.collapse { - height: 0; - overflow: hidden; - } - .navbar .btn-navbar { - display: block; - } - .navbar-static .navbar-inner { - padding-right: 10px; - padding-left: 10px; - } -} - -@media (min-width: 980px) { - .nav-collapse.collapse { - height: auto !important; - overflow: visible !important; - } -} diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap-responsive.min.css b/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap-responsive.min.css deleted file mode 100644 index 5cb833ff08..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap-responsive.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap Responsive v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */@-ms-viewport{width:device-width}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.564102564102564%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*margin-left:16.98763411529369%}.row-fluid .offset1{margin-left:11.11111111111111%;*margin-left:11.004728132387708%}.row-fluid .offset1:first-child{margin-left:8.547008547008547%;*margin-left:8.440625568285142%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:30px}input.span12,textarea.span12,.uneditable-input.span12{width:1156px}input.span11,textarea.span11,.uneditable-input.span11{width:1056px}input.span10,textarea.span10,.uneditable-input.span10{width:956px}input.span9,textarea.span9,.uneditable-input.span9{width:856px}input.span8,textarea.span8,.uneditable-input.span8{width:756px}input.span7,textarea.span7,.uneditable-input.span7{width:656px}input.span6,textarea.span6,.uneditable-input.span6{width:556px}input.span5,textarea.span5,.uneditable-input.span5{width:456px}input.span4,textarea.span4,.uneditable-input.span4{width:356px}input.span3,textarea.span3,.uneditable-input.span3{width:256px}input.span2,textarea.span2,.uneditable-input.span2{width:156px}input.span1,textarea.span1,.uneditable-input.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media(min-width:768px) and (max-width:979px){.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:724px}.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.7624309392265194%;*margin-left:2.709239449864817%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.7624309392265194%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.43646408839778%;*width:91.38327259903608%}.row-fluid .span10{width:82.87292817679558%;*width:82.81973668743387%}.row-fluid .span9{width:74.30939226519337%;*width:74.25620077583166%}.row-fluid .span8{width:65.74585635359117%;*width:65.69266486422946%}.row-fluid .span7{width:57.18232044198895%;*width:57.12912895262725%}.row-fluid .span6{width:48.61878453038674%;*width:48.56559304102504%}.row-fluid .span5{width:40.05524861878453%;*width:40.00205712942283%}.row-fluid .span4{width:31.491712707182323%;*width:31.43852121782062%}.row-fluid .span3{width:22.92817679558011%;*width:22.87498530621841%}.row-fluid .span2{width:14.3646408839779%;*width:14.311449394616199%}.row-fluid .span1{width:5.801104972375691%;*width:5.747913483013988%}.row-fluid .offset12{margin-left:105.52486187845304%;*margin-left:105.41847889972962%}.row-fluid .offset12:first-child{margin-left:102.76243093922652%;*margin-left:102.6560479605031%}.row-fluid .offset11{margin-left:96.96132596685082%;*margin-left:96.8549429881274%}.row-fluid .offset11:first-child{margin-left:94.1988950276243%;*margin-left:94.09251204890089%}.row-fluid .offset10{margin-left:88.39779005524862%;*margin-left:88.2914070765252%}.row-fluid .offset10:first-child{margin-left:85.6353591160221%;*margin-left:85.52897613729868%}.row-fluid .offset9{margin-left:79.8342541436464%;*margin-left:79.72787116492299%}.row-fluid .offset9:first-child{margin-left:77.07182320441989%;*margin-left:76.96544022569647%}.row-fluid .offset8{margin-left:71.2707182320442%;*margin-left:71.16433525332079%}.row-fluid .offset8:first-child{margin-left:68.50828729281768%;*margin-left:68.40190431409427%}.row-fluid .offset7{margin-left:62.70718232044199%;*margin-left:62.600799341718584%}.row-fluid .offset7:first-child{margin-left:59.94475138121547%;*margin-left:59.838368402492065%}.row-fluid .offset6{margin-left:54.14364640883978%;*margin-left:54.037263430116376%}.row-fluid .offset6:first-child{margin-left:51.38121546961326%;*margin-left:51.27483249088986%}.row-fluid .offset5{margin-left:45.58011049723757%;*margin-left:45.47372751851417%}.row-fluid .offset5:first-child{margin-left:42.81767955801105%;*margin-left:42.71129657928765%}.row-fluid .offset4{margin-left:37.01657458563536%;*margin-left:36.91019160691196%}.row-fluid .offset4:first-child{margin-left:34.25414364640884%;*margin-left:34.14776066768544%}.row-fluid .offset3{margin-left:28.45303867403315%;*margin-left:28.346655695309746%}.row-fluid .offset3:first-child{margin-left:25.69060773480663%;*margin-left:25.584224756083227%}.row-fluid .offset2{margin-left:19.88950276243094%;*margin-left:19.783119783707537%}.row-fluid .offset2:first-child{margin-left:17.12707182320442%;*margin-left:17.02068884448102%}.row-fluid .offset1{margin-left:11.32596685082873%;*margin-left:11.219583872105325%}.row-fluid .offset1:first-child{margin-left:8.56353591160221%;*margin-left:8.457152932878806%}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:710px}input.span11,textarea.span11,.uneditable-input.span11{width:648px}input.span10,textarea.span10,.uneditable-input.span10{width:586px}input.span9,textarea.span9,.uneditable-input.span9{width:524px}input.span8,textarea.span8,.uneditable-input.span8{width:462px}input.span7,textarea.span7,.uneditable-input.span7{width:400px}input.span6,textarea.span6,.uneditable-input.span6{width:338px}input.span5,textarea.span5,.uneditable-input.span5{width:276px}input.span4,textarea.span4,.uneditable-input.span4{width:214px}input.span3,textarea.span3,.uneditable-input.span3{width:152px}input.span2,textarea.span2,.uneditable-input.span2{width:90px}input.span1,textarea.span1,.uneditable-input.span1{width:28px}}@media(max-width:767px){body{padding-right:20px;padding-left:20px}.navbar-fixed-top,.navbar-fixed-bottom,.navbar-static-top{margin-right:-20px;margin-left:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;width:auto;clear:none;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}[class*="span"],.uneditable-input[class*="span"],.row-fluid [class*="span"]{display:block;float:none;width:100%;margin-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.span12,.row-fluid .span12{width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="offset"]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.input-prepend input,.input-append input,.input-prepend input[class*="span"],.input-append input[class*="span"]{display:inline-block;width:auto}.controls-row [class*="span"]+[class*="span"]{margin-left:0}.modal{position:fixed;top:20px;right:20px;left:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media(max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type="checkbox"],input[type="radio"]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-right:10px;padding-left:10px}.media .pull-left,.media .pull-right{display:block;float:none;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;right:10px;left:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media(max-width:979px){body{padding-top:0}.navbar-fixed-top,.navbar-fixed-bottom{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-right:10px;padding-left:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .nav>li>a,.nav-collapse .dropdown-menu a{padding:9px 15px;font-weight:bold;color:#777;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.nav-collapse .btn{padding:4px 10px 4px;font-weight:normal;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .nav>li>a:hover,.nav-collapse .dropdown-menu a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .nav>li>a,.navbar-inverse .nav-collapse .dropdown-menu a{color:#999}.navbar-inverse .nav-collapse .nav>li>a:hover,.navbar-inverse .nav-collapse .dropdown-menu a:hover{background-color:#111}.nav-collapse.in .btn-group{padding:0;margin-top:5px}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;display:none;float:none;max-width:none;padding:0;margin:0 15px;background-color:transparent;border:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu:before,.nav-collapse .dropdown-menu:after{display:none}.nav-collapse .dropdown-menu .divider{display:none}.nav-collapse .nav>li>.dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{height:0;overflow:hidden}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-right:10px;padding-left:10px}}@media(min-width:980px){.nav-collapse.collapse{height:auto!important;overflow:visible!important}} diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap.css b/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap.css deleted file mode 100644 index 8ab3cefcf7..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap.css +++ /dev/null @@ -1,6039 +0,0 @@ -/*! - * Bootstrap v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -nav, -section { - display: block; -} - -audio, -canvas, -video { - display: inline-block; - *display: inline; - *zoom: 1; -} - -audio:not([controls]) { - display: none; -} - -html { - font-size: 100%; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; -} - -a:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -a:hover, -a:active { - outline: 0; -} - -sub, -sup { - position: relative; - font-size: 75%; - line-height: 0; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -img { - width: auto\9; - height: auto; - max-width: 100%; - vertical-align: middle; - border: 0; - -ms-interpolation-mode: bicubic; -} - -#map_canvas img, -.google-maps img { - max-width: none; -} - -button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} - -button, -input { - *overflow: visible; - line-height: normal; -} - -button::-moz-focus-inner, -input::-moz-focus-inner { - padding: 0; - border: 0; -} - -button, -html input[type="button"], -input[type="reset"], -input[type="submit"] { - cursor: pointer; - -webkit-appearance: button; -} - -label, -select, -button, -input[type="button"], -input[type="reset"], -input[type="submit"], -input[type="radio"], -input[type="checkbox"] { - cursor: pointer; -} - -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-decoration, -input[type="search"]::-webkit-search-cancel-button { - -webkit-appearance: none; -} - -textarea { - overflow: auto; - vertical-align: top; -} - -@media print { - * { - color: #000 !important; - text-shadow: none !important; - background: transparent !important; - box-shadow: none !important; - } - a, - a:visited { - text-decoration: underline; - } - a[href]:after { - content: " (" attr(href) ")"; - } - abbr[title]:after { - content: " (" attr(title) ")"; - } - .ir a:after, - a[href^="javascript:"]:after, - a[href^="#"]:after { - content: ""; - } - pre, - blockquote { - border: 1px solid #999; - page-break-inside: avoid; - } - thead { - display: table-header-group; - } - tr, - img { - page-break-inside: avoid; - } - img { - max-width: 100% !important; - } - @page { - margin: 0.5cm; - } - p, - h2, - h3 { - orphans: 3; - widows: 3; - } - h2, - h3 { - page-break-after: avoid; - } -} - -.clearfix { - *zoom: 1; -} - -.clearfix:before, -.clearfix:after { - display: table; - line-height: 0; - content: ""; -} - -.clearfix:after { - clear: both; -} - -.hide-text { - font: 0/0 a; - color: transparent; - text-shadow: none; - background-color: transparent; - border: 0; -} - -.input-block-level { - display: block; - width: 100%; - min-height: 30px; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -body { - margin: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 20px; - color: #333333; - background-color: #ffffff; -} - -a { - color: #0088cc; - text-decoration: none; -} - -a:hover { - color: #005580; - text-decoration: underline; -} - -.img-rounded { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.img-polaroid { - padding: 4px; - background-color: #fff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); -} - -.img-circle { - -webkit-border-radius: 500px; - -moz-border-radius: 500px; - border-radius: 500px; -} - -.row { - margin-left: -20px; - *zoom: 1; -} - -.row:before, -.row:after { - display: table; - line-height: 0; - content: ""; -} - -.row:after { - clear: both; -} - -[class*="span"] { - float: left; - min-height: 1px; - margin-left: 20px; -} - -.container, -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} - -.span12 { - width: 940px; -} - -.span11 { - width: 860px; -} - -.span10 { - width: 780px; -} - -.span9 { - width: 700px; -} - -.span8 { - width: 620px; -} - -.span7 { - width: 540px; -} - -.span6 { - width: 460px; -} - -.span5 { - width: 380px; -} - -.span4 { - width: 300px; -} - -.span3 { - width: 220px; -} - -.span2 { - width: 140px; -} - -.span1 { - width: 60px; -} - -.offset12 { - margin-left: 980px; -} - -.offset11 { - margin-left: 900px; -} - -.offset10 { - margin-left: 820px; -} - -.offset9 { - margin-left: 740px; -} - -.offset8 { - margin-left: 660px; -} - -.offset7 { - margin-left: 580px; -} - -.offset6 { - margin-left: 500px; -} - -.offset5 { - margin-left: 420px; -} - -.offset4 { - margin-left: 340px; -} - -.offset3 { - margin-left: 260px; -} - -.offset2 { - margin-left: 180px; -} - -.offset1 { - margin-left: 100px; -} - -.row-fluid { - width: 100%; - *zoom: 1; -} - -.row-fluid:before, -.row-fluid:after { - display: table; - line-height: 0; - content: ""; -} - -.row-fluid:after { - clear: both; -} - -.row-fluid [class*="span"] { - display: block; - float: left; - width: 100%; - min-height: 30px; - margin-left: 2.127659574468085%; - *margin-left: 2.074468085106383%; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.row-fluid [class*="span"]:first-child { - margin-left: 0; -} - -.row-fluid .controls-row [class*="span"] + [class*="span"] { - margin-left: 2.127659574468085%; -} - -.row-fluid .span12 { - width: 100%; - *width: 99.94680851063829%; -} - -.row-fluid .span11 { - width: 91.48936170212765%; - *width: 91.43617021276594%; -} - -.row-fluid .span10 { - width: 82.97872340425532%; - *width: 82.92553191489361%; -} - -.row-fluid .span9 { - width: 74.46808510638297%; - *width: 74.41489361702126%; -} - -.row-fluid .span8 { - width: 65.95744680851064%; - *width: 65.90425531914893%; -} - -.row-fluid .span7 { - width: 57.44680851063829%; - *width: 57.39361702127659%; -} - -.row-fluid .span6 { - width: 48.93617021276595%; - *width: 48.88297872340425%; -} - -.row-fluid .span5 { - width: 40.42553191489362%; - *width: 40.37234042553192%; -} - -.row-fluid .span4 { - width: 31.914893617021278%; - *width: 31.861702127659576%; -} - -.row-fluid .span3 { - width: 23.404255319148934%; - *width: 23.351063829787233%; -} - -.row-fluid .span2 { - width: 14.893617021276595%; - *width: 14.840425531914894%; -} - -.row-fluid .span1 { - width: 6.382978723404255%; - *width: 6.329787234042553%; -} - -.row-fluid .offset12 { - margin-left: 104.25531914893617%; - *margin-left: 104.14893617021275%; -} - -.row-fluid .offset12:first-child { - margin-left: 102.12765957446808%; - *margin-left: 102.02127659574467%; -} - -.row-fluid .offset11 { - margin-left: 95.74468085106382%; - *margin-left: 95.6382978723404%; -} - -.row-fluid .offset11:first-child { - margin-left: 93.61702127659574%; - *margin-left: 93.51063829787232%; -} - -.row-fluid .offset10 { - margin-left: 87.23404255319149%; - *margin-left: 87.12765957446807%; -} - -.row-fluid .offset10:first-child { - margin-left: 85.1063829787234%; - *margin-left: 84.99999999999999%; -} - -.row-fluid .offset9 { - margin-left: 78.72340425531914%; - *margin-left: 78.61702127659572%; -} - -.row-fluid .offset9:first-child { - margin-left: 76.59574468085106%; - *margin-left: 76.48936170212764%; -} - -.row-fluid .offset8 { - margin-left: 70.2127659574468%; - *margin-left: 70.10638297872339%; -} - -.row-fluid .offset8:first-child { - margin-left: 68.08510638297872%; - *margin-left: 67.9787234042553%; -} - -.row-fluid .offset7 { - margin-left: 61.70212765957446%; - *margin-left: 61.59574468085106%; -} - -.row-fluid .offset7:first-child { - margin-left: 59.574468085106375%; - *margin-left: 59.46808510638297%; -} - -.row-fluid .offset6 { - margin-left: 53.191489361702125%; - *margin-left: 53.085106382978715%; -} - -.row-fluid .offset6:first-child { - margin-left: 51.063829787234035%; - *margin-left: 50.95744680851063%; -} - -.row-fluid .offset5 { - margin-left: 44.68085106382979%; - *margin-left: 44.57446808510638%; -} - -.row-fluid .offset5:first-child { - margin-left: 42.5531914893617%; - *margin-left: 42.4468085106383%; -} - -.row-fluid .offset4 { - margin-left: 36.170212765957444%; - *margin-left: 36.06382978723405%; -} - -.row-fluid .offset4:first-child { - margin-left: 34.04255319148936%; - *margin-left: 33.93617021276596%; -} - -.row-fluid .offset3 { - margin-left: 27.659574468085104%; - *margin-left: 27.5531914893617%; -} - -.row-fluid .offset3:first-child { - margin-left: 25.53191489361702%; - *margin-left: 25.425531914893618%; -} - -.row-fluid .offset2 { - margin-left: 19.148936170212764%; - *margin-left: 19.04255319148936%; -} - -.row-fluid .offset2:first-child { - margin-left: 17.02127659574468%; - *margin-left: 16.914893617021278%; -} - -.row-fluid .offset1 { - margin-left: 10.638297872340425%; - *margin-left: 10.53191489361702%; -} - -.row-fluid .offset1:first-child { - margin-left: 8.51063829787234%; - *margin-left: 8.404255319148938%; -} - -[class*="span"].hide, -.row-fluid [class*="span"].hide { - display: none; -} - -[class*="span"].pull-right, -.row-fluid [class*="span"].pull-right { - float: right; -} - -.container { - margin-right: auto; - margin-left: auto; - *zoom: 1; -} - -.container:before, -.container:after { - display: table; - line-height: 0; - content: ""; -} - -.container:after { - clear: both; -} - -.container-fluid { - padding-right: 20px; - padding-left: 20px; - *zoom: 1; -} - -.container-fluid:before, -.container-fluid:after { - display: table; - line-height: 0; - content: ""; -} - -.container-fluid:after { - clear: both; -} - -p { - margin: 0 0 10px; -} - -.lead { - margin-bottom: 20px; - font-size: 21px; - font-weight: 200; - line-height: 30px; -} - -small { - font-size: 85%; -} - -strong { - font-weight: bold; -} - -em { - font-style: italic; -} - -cite { - font-style: normal; -} - -.muted { - color: #999999; -} - -a.muted:hover { - color: #808080; -} - -.text-warning { - color: #c09853; -} - -a.text-warning:hover { - color: #a47e3c; -} - -.text-error { - color: #b94a48; -} - -a.text-error:hover { - color: #953b39; -} - -.text-info { - color: #3a87ad; -} - -a.text-info:hover { - color: #2d6987; -} - -.text-success { - color: #468847; -} - -a.text-success:hover { - color: #356635; -} - -h1, -h2, -h3, -h4, -h5, -h6 { - margin: 10px 0; - font-family: inherit; - font-weight: bold; - line-height: 20px; - color: inherit; - text-rendering: optimizelegibility; -} - -h1 small, -h2 small, -h3 small, -h4 small, -h5 small, -h6 small { - font-weight: normal; - line-height: 1; - color: #999999; -} - -h1, -h2, -h3 { - line-height: 40px; -} - -h1 { - font-size: 38.5px; -} - -h2 { - font-size: 31.5px; -} - -h3 { - font-size: 24.5px; -} - -h4 { - font-size: 17.5px; -} - -h5 { - font-size: 14px; -} - -h6 { - font-size: 11.9px; -} - -h1 small { - font-size: 24.5px; -} - -h2 small { - font-size: 17.5px; -} - -h3 small { - font-size: 14px; -} - -h4 small { - font-size: 14px; -} - -.page-header { - padding-bottom: 9px; - margin: 20px 0 30px; - border-bottom: 1px solid #eeeeee; -} - -ul, -ol { - padding: 0; - margin: 0 0 10px 25px; -} - -ul ul, -ul ol, -ol ol, -ol ul { - margin-bottom: 0; -} - -li { - line-height: 20px; -} - -ul.unstyled, -ol.unstyled { - margin-left: 0; - list-style: none; -} - -ul.inline, -ol.inline { - margin-left: 0; - list-style: none; -} - -ul.inline > li, -ol.inline > li { - display: inline-block; - padding-right: 5px; - padding-left: 5px; -} - -dl { - margin-bottom: 20px; -} - -dt, -dd { - line-height: 20px; -} - -dt { - font-weight: bold; -} - -dd { - margin-left: 10px; -} - -.dl-horizontal { - *zoom: 1; -} - -.dl-horizontal:before, -.dl-horizontal:after { - display: table; - line-height: 0; - content: ""; -} - -.dl-horizontal:after { - clear: both; -} - -.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; -} - -hr { - margin: 20px 0; - border: 0; - border-top: 1px solid #eeeeee; - border-bottom: 1px solid #ffffff; -} - -abbr[title], -abbr[data-original-title] { - cursor: help; - border-bottom: 1px dotted #999999; -} - -abbr.initialism { - font-size: 90%; - text-transform: uppercase; -} - -blockquote { - padding: 0 0 0 15px; - margin: 0 0 20px; - border-left: 5px solid #eeeeee; -} - -blockquote p { - margin-bottom: 0; - font-size: 16px; - font-weight: 300; - line-height: 25px; -} - -blockquote small { - display: block; - line-height: 20px; - color: #999999; -} - -blockquote small:before { - content: '\2014 \00A0'; -} - -blockquote.pull-right { - float: right; - padding-right: 15px; - padding-left: 0; - border-right: 5px solid #eeeeee; - border-left: 0; -} - -blockquote.pull-right p, -blockquote.pull-right small { - text-align: right; -} - -blockquote.pull-right small:before { - content: ''; -} - -blockquote.pull-right small:after { - content: '\00A0 \2014'; -} - -q:before, -q:after, -blockquote:before, -blockquote:after { - content: ""; -} - -address { - display: block; - margin-bottom: 20px; - font-style: normal; - line-height: 20px; -} - -code, -pre { - padding: 0 3px 2px; - font-family: Monaco, Menlo, Consolas, "Courier New", monospace; - font-size: 12px; - color: #333333; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -code { - padding: 2px 4px; - color: #d14; - white-space: nowrap; - background-color: #f7f7f9; - border: 1px solid #e1e1e8; -} - -pre { - display: block; - padding: 9.5px; - margin: 0 0 10px; - font-size: 13px; - line-height: 20px; - word-break: break-all; - word-wrap: break-word; - white-space: pre; - white-space: pre-wrap; - background-color: #f5f5f5; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.15); - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -pre.prettyprint { - margin-bottom: 20px; -} - -pre code { - padding: 0; - color: inherit; - white-space: pre; - white-space: pre-wrap; - background-color: transparent; - border: 0; -} - -.pre-scrollable { - max-height: 340px; - overflow-y: scroll; -} - -form { - margin: 0 0 20px; -} - -fieldset { - padding: 0; - margin: 0; - border: 0; -} - -legend { - display: block; - width: 100%; - padding: 0; - margin-bottom: 20px; - font-size: 21px; - line-height: 40px; - color: #333333; - border: 0; - border-bottom: 1px solid #e5e5e5; -} - -legend small { - font-size: 15px; - color: #999999; -} - -label, -input, -button, -select, -textarea { - font-size: 14px; - font-weight: normal; - line-height: 20px; -} - -input, -button, -select, -textarea { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -} - -label { - display: block; - margin-bottom: 5px; -} - -select, -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - display: inline-block; - height: 20px; - padding: 4px 6px; - margin-bottom: 10px; - font-size: 14px; - line-height: 20px; - color: #555555; - vertical-align: middle; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -input, -textarea, -.uneditable-input { - width: 206px; -} - -textarea { - height: auto; -} - -textarea, -input[type="text"], -input[type="password"], -input[type="datetime"], -input[type="datetime-local"], -input[type="date"], -input[type="month"], -input[type="time"], -input[type="week"], -input[type="number"], -input[type="email"], -input[type="url"], -input[type="search"], -input[type="tel"], -input[type="color"], -.uneditable-input { - background-color: #ffffff; - border: 1px solid #cccccc; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; - -moz-transition: border linear 0.2s, box-shadow linear 0.2s; - -o-transition: border linear 0.2s, box-shadow linear 0.2s; - transition: border linear 0.2s, box-shadow linear 0.2s; -} - -textarea:focus, -input[type="text"]:focus, -input[type="password"]:focus, -input[type="datetime"]:focus, -input[type="datetime-local"]:focus, -input[type="date"]:focus, -input[type="month"]:focus, -input[type="time"]:focus, -input[type="week"]:focus, -input[type="number"]:focus, -input[type="email"]:focus, -input[type="url"]:focus, -input[type="search"]:focus, -input[type="tel"]:focus, -input[type="color"]:focus, -.uneditable-input:focus { - border-color: rgba(82, 168, 236, 0.8); - outline: 0; - outline: thin dotted \9; - /* IE6-9 */ - - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); -} - -input[type="radio"], -input[type="checkbox"] { - margin: 4px 0 0; - margin-top: 1px \9; - *margin-top: 0; - line-height: normal; -} - -input[type="file"], -input[type="image"], -input[type="submit"], -input[type="reset"], -input[type="button"], -input[type="radio"], -input[type="checkbox"] { - width: auto; -} - -select, -input[type="file"] { - height: 30px; - /* In IE7, the height of the select element cannot be changed by height, only font-size */ - - *margin-top: 4px; - /* For IE7, add top margin to align select with labels */ - - line-height: 30px; -} - -select { - width: 220px; - background-color: #ffffff; - border: 1px solid #cccccc; -} - -select[multiple], -select[size] { - height: auto; -} - -select:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.uneditable-input, -.uneditable-textarea { - color: #999999; - cursor: not-allowed; - background-color: #fcfcfc; - border-color: #cccccc; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); -} - -.uneditable-input { - overflow: hidden; - white-space: nowrap; -} - -.uneditable-textarea { - width: auto; - height: auto; -} - -input:-moz-placeholder, -textarea:-moz-placeholder { - color: #999999; -} - -input:-ms-input-placeholder, -textarea:-ms-input-placeholder { - color: #999999; -} - -input::-webkit-input-placeholder, -textarea::-webkit-input-placeholder { - color: #999999; -} - -.radio, -.checkbox { - min-height: 20px; - padding-left: 20px; -} - -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: -20px; -} - -.controls > .radio:first-child, -.controls > .checkbox:first-child { - padding-top: 5px; -} - -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} - -.radio.inline + .radio.inline, -.checkbox.inline + .checkbox.inline { - margin-left: 10px; -} - -.input-mini { - width: 60px; -} - -.input-small { - width: 90px; -} - -.input-medium { - width: 150px; -} - -.input-large { - width: 210px; -} - -.input-xlarge { - width: 270px; -} - -.input-xxlarge { - width: 530px; -} - -input[class*="span"], -select[class*="span"], -textarea[class*="span"], -.uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"] { - float: none; - margin-left: 0; -} - -.input-append input[class*="span"], -.input-append .uneditable-input[class*="span"], -.input-prepend input[class*="span"], -.input-prepend .uneditable-input[class*="span"], -.row-fluid input[class*="span"], -.row-fluid select[class*="span"], -.row-fluid textarea[class*="span"], -.row-fluid .uneditable-input[class*="span"], -.row-fluid .input-prepend [class*="span"], -.row-fluid .input-append [class*="span"] { - display: inline-block; -} - -input, -textarea, -.uneditable-input { - margin-left: 0; -} - -.controls-row [class*="span"] + [class*="span"] { - margin-left: 20px; -} - -input.span12, -textarea.span12, -.uneditable-input.span12 { - width: 926px; -} - -input.span11, -textarea.span11, -.uneditable-input.span11 { - width: 846px; -} - -input.span10, -textarea.span10, -.uneditable-input.span10 { - width: 766px; -} - -input.span9, -textarea.span9, -.uneditable-input.span9 { - width: 686px; -} - -input.span8, -textarea.span8, -.uneditable-input.span8 { - width: 606px; -} - -input.span7, -textarea.span7, -.uneditable-input.span7 { - width: 526px; -} - -input.span6, -textarea.span6, -.uneditable-input.span6 { - width: 446px; -} - -input.span5, -textarea.span5, -.uneditable-input.span5 { - width: 366px; -} - -input.span4, -textarea.span4, -.uneditable-input.span4 { - width: 286px; -} - -input.span3, -textarea.span3, -.uneditable-input.span3 { - width: 206px; -} - -input.span2, -textarea.span2, -.uneditable-input.span2 { - width: 126px; -} - -input.span1, -textarea.span1, -.uneditable-input.span1 { - width: 46px; -} - -.controls-row { - *zoom: 1; -} - -.controls-row:before, -.controls-row:after { - display: table; - line-height: 0; - content: ""; -} - -.controls-row:after { - clear: both; -} - -.controls-row [class*="span"], -.row-fluid .controls-row [class*="span"] { - float: left; -} - -.controls-row .checkbox[class*="span"], -.controls-row .radio[class*="span"] { - padding-top: 5px; -} - -input[disabled], -select[disabled], -textarea[disabled], -input[readonly], -select[readonly], -textarea[readonly] { - cursor: not-allowed; - background-color: #eeeeee; -} - -input[type="radio"][disabled], -input[type="checkbox"][disabled], -input[type="radio"][readonly], -input[type="checkbox"][readonly] { - background-color: transparent; -} - -.control-group.warning .control-label, -.control-group.warning .help-block, -.control-group.warning .help-inline { - color: #c09853; -} - -.control-group.warning .checkbox, -.control-group.warning .radio, -.control-group.warning input, -.control-group.warning select, -.control-group.warning textarea { - color: #c09853; -} - -.control-group.warning input, -.control-group.warning select, -.control-group.warning textarea { - border-color: #c09853; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.warning input:focus, -.control-group.warning select:focus, -.control-group.warning textarea:focus { - border-color: #a47e3c; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e; -} - -.control-group.warning .input-prepend .add-on, -.control-group.warning .input-append .add-on { - color: #c09853; - background-color: #fcf8e3; - border-color: #c09853; -} - -.control-group.error .control-label, -.control-group.error .help-block, -.control-group.error .help-inline { - color: #b94a48; -} - -.control-group.error .checkbox, -.control-group.error .radio, -.control-group.error input, -.control-group.error select, -.control-group.error textarea { - color: #b94a48; -} - -.control-group.error input, -.control-group.error select, -.control-group.error textarea { - border-color: #b94a48; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.error input:focus, -.control-group.error select:focus, -.control-group.error textarea:focus { - border-color: #953b39; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; -} - -.control-group.error .input-prepend .add-on, -.control-group.error .input-append .add-on { - color: #b94a48; - background-color: #f2dede; - border-color: #b94a48; -} - -.control-group.success .control-label, -.control-group.success .help-block, -.control-group.success .help-inline { - color: #468847; -} - -.control-group.success .checkbox, -.control-group.success .radio, -.control-group.success input, -.control-group.success select, -.control-group.success textarea { - color: #468847; -} - -.control-group.success input, -.control-group.success select, -.control-group.success textarea { - border-color: #468847; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.success input:focus, -.control-group.success select:focus, -.control-group.success textarea:focus { - border-color: #356635; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b; -} - -.control-group.success .input-prepend .add-on, -.control-group.success .input-append .add-on { - color: #468847; - background-color: #dff0d8; - border-color: #468847; -} - -.control-group.info .control-label, -.control-group.info .help-block, -.control-group.info .help-inline { - color: #3a87ad; -} - -.control-group.info .checkbox, -.control-group.info .radio, -.control-group.info input, -.control-group.info select, -.control-group.info textarea { - color: #3a87ad; -} - -.control-group.info input, -.control-group.info select, -.control-group.info textarea { - border-color: #3a87ad; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); -} - -.control-group.info input:focus, -.control-group.info select:focus, -.control-group.info textarea:focus { - border-color: #2d6987; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3; -} - -.control-group.info .input-prepend .add-on, -.control-group.info .input-append .add-on { - color: #3a87ad; - background-color: #d9edf7; - border-color: #3a87ad; -} - -input:focus:invalid, -textarea:focus:invalid, -select:focus:invalid { - color: #b94a48; - border-color: #ee5f5b; -} - -input:focus:invalid:focus, -textarea:focus:invalid:focus, -select:focus:invalid:focus { - border-color: #e9322d; - -webkit-box-shadow: 0 0 6px #f8b9b7; - -moz-box-shadow: 0 0 6px #f8b9b7; - box-shadow: 0 0 6px #f8b9b7; -} - -.form-actions { - padding: 19px 20px 20px; - margin-top: 20px; - margin-bottom: 20px; - background-color: #f5f5f5; - border-top: 1px solid #e5e5e5; - *zoom: 1; -} - -.form-actions:before, -.form-actions:after { - display: table; - line-height: 0; - content: ""; -} - -.form-actions:after { - clear: both; -} - -.help-block, -.help-inline { - color: #595959; -} - -.help-block { - display: block; - margin-bottom: 10px; -} - -.help-inline { - display: inline-block; - *display: inline; - padding-left: 5px; - vertical-align: middle; - *zoom: 1; -} - -.input-append, -.input-prepend { - margin-bottom: 5px; - font-size: 0; - white-space: nowrap; -} - -.input-append input, -.input-prepend input, -.input-append select, -.input-prepend select, -.input-append .uneditable-input, -.input-prepend .uneditable-input, -.input-append .dropdown-menu, -.input-prepend .dropdown-menu { - font-size: 14px; -} - -.input-append input, -.input-prepend input, -.input-append select, -.input-prepend select, -.input-append .uneditable-input, -.input-prepend .uneditable-input { - position: relative; - margin-bottom: 0; - *margin-left: 0; - vertical-align: top; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-append input:focus, -.input-prepend input:focus, -.input-append select:focus, -.input-prepend select:focus, -.input-append .uneditable-input:focus, -.input-prepend .uneditable-input:focus { - z-index: 2; -} - -.input-append .add-on, -.input-prepend .add-on { - display: inline-block; - width: auto; - height: 20px; - min-width: 16px; - padding: 4px 5px; - font-size: 14px; - font-weight: normal; - line-height: 20px; - text-align: center; - text-shadow: 0 1px 0 #ffffff; - background-color: #eeeeee; - border: 1px solid #ccc; -} - -.input-append .add-on, -.input-prepend .add-on, -.input-append .btn, -.input-prepend .btn, -.input-append .btn-group > .dropdown-toggle, -.input-prepend .btn-group > .dropdown-toggle { - vertical-align: top; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.input-append .active, -.input-prepend .active { - background-color: #a9dba9; - border-color: #46a546; -} - -.input-prepend .add-on, -.input-prepend .btn { - margin-right: -1px; -} - -.input-prepend .add-on:first-child, -.input-prepend .btn:first-child { - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-append input, -.input-append select, -.input-append .uneditable-input { - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-append input + .btn-group .btn:last-child, -.input-append select + .btn-group .btn:last-child, -.input-append .uneditable-input + .btn-group .btn:last-child { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-append .add-on, -.input-append .btn, -.input-append .btn-group { - margin-left: -1px; -} - -.input-append .add-on:last-child, -.input-append .btn:last-child, -.input-append .btn-group:last-child > .dropdown-toggle { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append input, -.input-prepend.input-append select, -.input-prepend.input-append .uneditable-input { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.input-prepend.input-append input + .btn-group .btn, -.input-prepend.input-append select + .btn-group .btn, -.input-prepend.input-append .uneditable-input + .btn-group .btn { - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append .add-on:first-child, -.input-prepend.input-append .btn:first-child { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.input-prepend.input-append .add-on:last-child, -.input-prepend.input-append .btn:last-child { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.input-prepend.input-append .btn-group:first-child { - margin-left: 0; -} - -input.search-query { - padding-right: 14px; - padding-right: 4px \9; - padding-left: 14px; - padding-left: 4px \9; - /* IE7-8 doesn't have border-radius, so don't indent the padding */ - - margin-bottom: 0; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -/* Allow for input prepend/append in search forms */ - -.form-search .input-append .search-query, -.form-search .input-prepend .search-query { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.form-search .input-append .search-query { - -webkit-border-radius: 14px 0 0 14px; - -moz-border-radius: 14px 0 0 14px; - border-radius: 14px 0 0 14px; -} - -.form-search .input-append .btn { - -webkit-border-radius: 0 14px 14px 0; - -moz-border-radius: 0 14px 14px 0; - border-radius: 0 14px 14px 0; -} - -.form-search .input-prepend .search-query { - -webkit-border-radius: 0 14px 14px 0; - -moz-border-radius: 0 14px 14px 0; - border-radius: 0 14px 14px 0; -} - -.form-search .input-prepend .btn { - -webkit-border-radius: 14px 0 0 14px; - -moz-border-radius: 14px 0 0 14px; - border-radius: 14px 0 0 14px; -} - -.form-search input, -.form-inline input, -.form-horizontal input, -.form-search textarea, -.form-inline textarea, -.form-horizontal textarea, -.form-search select, -.form-inline select, -.form-horizontal select, -.form-search .help-inline, -.form-inline .help-inline, -.form-horizontal .help-inline, -.form-search .uneditable-input, -.form-inline .uneditable-input, -.form-horizontal .uneditable-input, -.form-search .input-prepend, -.form-inline .input-prepend, -.form-horizontal .input-prepend, -.form-search .input-append, -.form-inline .input-append, -.form-horizontal .input-append { - display: inline-block; - *display: inline; - margin-bottom: 0; - vertical-align: middle; - *zoom: 1; -} - -.form-search .hide, -.form-inline .hide, -.form-horizontal .hide { - display: none; -} - -.form-search label, -.form-inline label, -.form-search .btn-group, -.form-inline .btn-group { - display: inline-block; -} - -.form-search .input-append, -.form-inline .input-append, -.form-search .input-prepend, -.form-inline .input-prepend { - margin-bottom: 0; -} - -.form-search .radio, -.form-search .checkbox, -.form-inline .radio, -.form-inline .checkbox { - padding-left: 0; - margin-bottom: 0; - vertical-align: middle; -} - -.form-search .radio input[type="radio"], -.form-search .checkbox input[type="checkbox"], -.form-inline .radio input[type="radio"], -.form-inline .checkbox input[type="checkbox"] { - float: left; - margin-right: 3px; - margin-left: 0; -} - -.control-group { - margin-bottom: 10px; -} - -legend + .control-group { - margin-top: 20px; - -webkit-margin-top-collapse: separate; -} - -.form-horizontal .control-group { - margin-bottom: 20px; - *zoom: 1; -} - -.form-horizontal .control-group:before, -.form-horizontal .control-group:after { - display: table; - line-height: 0; - content: ""; -} - -.form-horizontal .control-group:after { - clear: both; -} - -.form-horizontal .control-label { - float: left; - width: 160px; - padding-top: 5px; - text-align: right; -} - -.form-horizontal .controls { - *display: inline-block; - *padding-left: 20px; - margin-left: 180px; - *margin-left: 0; -} - -.form-horizontal .controls:first-child { - *padding-left: 180px; -} - -.form-horizontal .help-block { - margin-bottom: 0; -} - -.form-horizontal input + .help-block, -.form-horizontal select + .help-block, -.form-horizontal textarea + .help-block, -.form-horizontal .uneditable-input + .help-block, -.form-horizontal .input-prepend + .help-block, -.form-horizontal .input-append + .help-block { - margin-top: 10px; -} - -.form-horizontal .form-actions { - padding-left: 180px; -} - -table { - max-width: 100%; - background-color: transparent; - border-collapse: collapse; - border-spacing: 0; -} - -.table { - width: 100%; - margin-bottom: 20px; -} - -.table th, -.table td { - padding: 8px; - line-height: 20px; - text-align: left; - vertical-align: top; - border-top: 1px solid #dddddd; -} - -.table th { - font-weight: bold; -} - -.table thead th { - vertical-align: bottom; -} - -.table caption + thead tr:first-child th, -.table caption + thead tr:first-child td, -.table colgroup + thead tr:first-child th, -.table colgroup + thead tr:first-child td, -.table thead:first-child tr:first-child th, -.table thead:first-child tr:first-child td { - border-top: 0; -} - -.table tbody + tbody { - border-top: 2px solid #dddddd; -} - -.table .table { - background-color: #ffffff; -} - -.table-condensed th, -.table-condensed td { - padding: 4px 5px; -} - -.table-bordered { - border: 1px solid #dddddd; - border-collapse: separate; - *border-collapse: collapse; - border-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.table-bordered th, -.table-bordered td { - border-left: 1px solid #dddddd; -} - -.table-bordered caption + thead tr:first-child th, -.table-bordered caption + tbody tr:first-child th, -.table-bordered caption + tbody tr:first-child td, -.table-bordered colgroup + thead tr:first-child th, -.table-bordered colgroup + tbody tr:first-child th, -.table-bordered colgroup + tbody tr:first-child td, -.table-bordered thead:first-child tr:first-child th, -.table-bordered tbody:first-child tr:first-child th, -.table-bordered tbody:first-child tr:first-child td { - border-top: 0; -} - -.table-bordered thead:first-child tr:first-child > th:first-child, -.table-bordered tbody:first-child tr:first-child > td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} - -.table-bordered thead:first-child tr:first-child > th:last-child, -.table-bordered tbody:first-child tr:first-child > td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} - -.table-bordered thead:last-child tr:last-child > th:first-child, -.table-bordered tbody:last-child tr:last-child > td:first-child, -.table-bordered tfoot:last-child tr:last-child > td:first-child { - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; -} - -.table-bordered thead:last-child tr:last-child > th:last-child, -.table-bordered tbody:last-child tr:last-child > td:last-child, -.table-bordered tfoot:last-child tr:last-child > td:last-child { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-bottomright: 4px; -} - -.table-bordered tfoot + tbody:last-child tr:last-child td:first-child { - -webkit-border-bottom-left-radius: 0; - border-bottom-left-radius: 0; - -moz-border-radius-bottomleft: 0; -} - -.table-bordered tfoot + tbody:last-child tr:last-child td:last-child { - -webkit-border-bottom-right-radius: 0; - border-bottom-right-radius: 0; - -moz-border-radius-bottomright: 0; -} - -.table-bordered caption + thead tr:first-child th:first-child, -.table-bordered caption + tbody tr:first-child td:first-child, -.table-bordered colgroup + thead tr:first-child th:first-child, -.table-bordered colgroup + tbody tr:first-child td:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; -} - -.table-bordered caption + thead tr:first-child th:last-child, -.table-bordered caption + tbody tr:first-child td:last-child, -.table-bordered colgroup + thead tr:first-child th:last-child, -.table-bordered colgroup + tbody tr:first-child td:last-child { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; -} - -.table-striped tbody > tr:nth-child(odd) > td, -.table-striped tbody > tr:nth-child(odd) > th { - background-color: #f9f9f9; -} - -.table-hover tbody tr:hover td, -.table-hover tbody tr:hover th { - background-color: #f5f5f5; -} - -table td[class*="span"], -table th[class*="span"], -.row-fluid table td[class*="span"], -.row-fluid table th[class*="span"] { - display: table-cell; - float: none; - margin-left: 0; -} - -.table td.span1, -.table th.span1 { - float: none; - width: 44px; - margin-left: 0; -} - -.table td.span2, -.table th.span2 { - float: none; - width: 124px; - margin-left: 0; -} - -.table td.span3, -.table th.span3 { - float: none; - width: 204px; - margin-left: 0; -} - -.table td.span4, -.table th.span4 { - float: none; - width: 284px; - margin-left: 0; -} - -.table td.span5, -.table th.span5 { - float: none; - width: 364px; - margin-left: 0; -} - -.table td.span6, -.table th.span6 { - float: none; - width: 444px; - margin-left: 0; -} - -.table td.span7, -.table th.span7 { - float: none; - width: 524px; - margin-left: 0; -} - -.table td.span8, -.table th.span8 { - float: none; - width: 604px; - margin-left: 0; -} - -.table td.span9, -.table th.span9 { - float: none; - width: 684px; - margin-left: 0; -} - -.table td.span10, -.table th.span10 { - float: none; - width: 764px; - margin-left: 0; -} - -.table td.span11, -.table th.span11 { - float: none; - width: 844px; - margin-left: 0; -} - -.table td.span12, -.table th.span12 { - float: none; - width: 924px; - margin-left: 0; -} - -.table tbody tr.success td { - background-color: #dff0d8; -} - -.table tbody tr.error td { - background-color: #f2dede; -} - -.table tbody tr.warning td { - background-color: #fcf8e3; -} - -.table tbody tr.info td { - background-color: #d9edf7; -} - -.table-hover tbody tr.success:hover td { - background-color: #d0e9c6; -} - -.table-hover tbody tr.error:hover td { - background-color: #ebcccc; -} - -.table-hover tbody tr.warning:hover td { - background-color: #faf2cc; -} - -.table-hover tbody tr.info:hover td { - background-color: #c4e3f3; -} - -[class^="icon-"], -[class*=" icon-"] { - display: inline-block; - width: 14px; - height: 14px; - margin-top: 1px; - *margin-right: .3em; - line-height: 14px; - vertical-align: text-top; - background-image: url("../img/glyphicons-halflings.png"); - background-position: 14px 14px; - background-repeat: no-repeat; -} - -/* White icons with optional class, or on hover/active states of certain elements */ - -.icon-white, -.nav-pills > .active > a > [class^="icon-"], -.nav-pills > .active > a > [class*=" icon-"], -.nav-list > .active > a > [class^="icon-"], -.nav-list > .active > a > [class*=" icon-"], -.navbar-inverse .nav > .active > a > [class^="icon-"], -.navbar-inverse .nav > .active > a > [class*=" icon-"], -.dropdown-menu > li > a:hover > [class^="icon-"], -.dropdown-menu > li > a:hover > [class*=" icon-"], -.dropdown-menu > .active > a > [class^="icon-"], -.dropdown-menu > .active > a > [class*=" icon-"], -.dropdown-submenu:hover > a > [class^="icon-"], -.dropdown-submenu:hover > a > [class*=" icon-"] { - background-image: url("../img/glyphicons-halflings-white.png"); -} - -.icon-glass { - background-position: 0 0; -} - -.icon-music { - background-position: -24px 0; -} - -.icon-search { - background-position: -48px 0; -} - -.icon-envelope { - background-position: -72px 0; -} - -.icon-heart { - background-position: -96px 0; -} - -.icon-star { - background-position: -120px 0; -} - -.icon-star-empty { - background-position: -144px 0; -} - -.icon-user { - background-position: -168px 0; -} - -.icon-film { - background-position: -192px 0; -} - -.icon-th-large { - background-position: -216px 0; -} - -.icon-th { - background-position: -240px 0; -} - -.icon-th-list { - background-position: -264px 0; -} - -.icon-ok { - background-position: -288px 0; -} - -.icon-remove { - background-position: -312px 0; -} - -.icon-zoom-in { - background-position: -336px 0; -} - -.icon-zoom-out { - background-position: -360px 0; -} - -.icon-off { - background-position: -384px 0; -} - -.icon-signal { - background-position: -408px 0; -} - -.icon-cog { - background-position: -432px 0; -} - -.icon-trash { - background-position: -456px 0; -} - -.icon-home { - background-position: 0 -24px; -} - -.icon-file { - background-position: -24px -24px; -} - -.icon-time { - background-position: -48px -24px; -} - -.icon-road { - background-position: -72px -24px; -} - -.icon-download-alt { - background-position: -96px -24px; -} - -.icon-download { - background-position: -120px -24px; -} - -.icon-upload { - background-position: -144px -24px; -} - -.icon-inbox { - background-position: -168px -24px; -} - -.icon-play-circle { - background-position: -192px -24px; -} - -.icon-repeat { - background-position: -216px -24px; -} - -.icon-refresh { - background-position: -240px -24px; -} - -.icon-list-alt { - background-position: -264px -24px; -} - -.icon-lock { - background-position: -287px -24px; -} - -.icon-flag { - background-position: -312px -24px; -} - -.icon-headphones { - background-position: -336px -24px; -} - -.icon-volume-off { - background-position: -360px -24px; -} - -.icon-volume-down { - background-position: -384px -24px; -} - -.icon-volume-up { - background-position: -408px -24px; -} - -.icon-qrcode { - background-position: -432px -24px; -} - -.icon-barcode { - background-position: -456px -24px; -} - -.icon-tag { - background-position: 0 -48px; -} - -.icon-tags { - background-position: -25px -48px; -} - -.icon-book { - background-position: -48px -48px; -} - -.icon-bookmark { - background-position: -72px -48px; -} - -.icon-print { - background-position: -96px -48px; -} - -.icon-camera { - background-position: -120px -48px; -} - -.icon-font { - background-position: -144px -48px; -} - -.icon-bold { - background-position: -167px -48px; -} - -.icon-italic { - background-position: -192px -48px; -} - -.icon-text-height { - background-position: -216px -48px; -} - -.icon-text-width { - background-position: -240px -48px; -} - -.icon-align-left { - background-position: -264px -48px; -} - -.icon-align-center { - background-position: -288px -48px; -} - -.icon-align-right { - background-position: -312px -48px; -} - -.icon-align-justify { - background-position: -336px -48px; -} - -.icon-list { - background-position: -360px -48px; -} - -.icon-indent-left { - background-position: -384px -48px; -} - -.icon-indent-right { - background-position: -408px -48px; -} - -.icon-facetime-video { - background-position: -432px -48px; -} - -.icon-picture { - background-position: -456px -48px; -} - -.icon-pencil { - background-position: 0 -72px; -} - -.icon-map-marker { - background-position: -24px -72px; -} - -.icon-adjust { - background-position: -48px -72px; -} - -.icon-tint { - background-position: -72px -72px; -} - -.icon-edit { - background-position: -96px -72px; -} - -.icon-share { - background-position: -120px -72px; -} - -.icon-check { - background-position: -144px -72px; -} - -.icon-move { - background-position: -168px -72px; -} - -.icon-step-backward { - background-position: -192px -72px; -} - -.icon-fast-backward { - background-position: -216px -72px; -} - -.icon-backward { - background-position: -240px -72px; -} - -.icon-play { - background-position: -264px -72px; -} - -.icon-pause { - background-position: -288px -72px; -} - -.icon-stop { - background-position: -312px -72px; -} - -.icon-forward { - background-position: -336px -72px; -} - -.icon-fast-forward { - background-position: -360px -72px; -} - -.icon-step-forward { - background-position: -384px -72px; -} - -.icon-eject { - background-position: -408px -72px; -} - -.icon-chevron-left { - background-position: -432px -72px; -} - -.icon-chevron-right { - background-position: -456px -72px; -} - -.icon-plus-sign { - background-position: 0 -96px; -} - -.icon-minus-sign { - background-position: -24px -96px; -} - -.icon-remove-sign { - background-position: -48px -96px; -} - -.icon-ok-sign { - background-position: -72px -96px; -} - -.icon-question-sign { - background-position: -96px -96px; -} - -.icon-info-sign { - background-position: -120px -96px; -} - -.icon-screenshot { - background-position: -144px -96px; -} - -.icon-remove-circle { - background-position: -168px -96px; -} - -.icon-ok-circle { - background-position: -192px -96px; -} - -.icon-ban-circle { - background-position: -216px -96px; -} - -.icon-arrow-left { - background-position: -240px -96px; -} - -.icon-arrow-right { - background-position: -264px -96px; -} - -.icon-arrow-up { - background-position: -289px -96px; -} - -.icon-arrow-down { - background-position: -312px -96px; -} - -.icon-share-alt { - background-position: -336px -96px; -} - -.icon-resize-full { - background-position: -360px -96px; -} - -.icon-resize-small { - background-position: -384px -96px; -} - -.icon-plus { - background-position: -408px -96px; -} - -.icon-minus { - background-position: -433px -96px; -} - -.icon-asterisk { - background-position: -456px -96px; -} - -.icon-exclamation-sign { - background-position: 0 -120px; -} - -.icon-gift { - background-position: -24px -120px; -} - -.icon-leaf { - background-position: -48px -120px; -} - -.icon-fire { - background-position: -72px -120px; -} - -.icon-eye-open { - background-position: -96px -120px; -} - -.icon-eye-close { - background-position: -120px -120px; -} - -.icon-warning-sign { - background-position: -144px -120px; -} - -.icon-plane { - background-position: -168px -120px; -} - -.icon-calendar { - background-position: -192px -120px; -} - -.icon-random { - width: 16px; - background-position: -216px -120px; -} - -.icon-comment { - background-position: -240px -120px; -} - -.icon-magnet { - background-position: -264px -120px; -} - -.icon-chevron-up { - background-position: -288px -120px; -} - -.icon-chevron-down { - background-position: -313px -119px; -} - -.icon-retweet { - background-position: -336px -120px; -} - -.icon-shopping-cart { - background-position: -360px -120px; -} - -.icon-folder-close { - background-position: -384px -120px; -} - -.icon-folder-open { - width: 16px; - background-position: -408px -120px; -} - -.icon-resize-vertical { - background-position: -432px -119px; -} - -.icon-resize-horizontal { - background-position: -456px -118px; -} - -.icon-hdd { - background-position: 0 -144px; -} - -.icon-bullhorn { - background-position: -24px -144px; -} - -.icon-bell { - background-position: -48px -144px; -} - -.icon-certificate { - background-position: -72px -144px; -} - -.icon-thumbs-up { - background-position: -96px -144px; -} - -.icon-thumbs-down { - background-position: -120px -144px; -} - -.icon-hand-right { - background-position: -144px -144px; -} - -.icon-hand-left { - background-position: -168px -144px; -} - -.icon-hand-up { - background-position: -192px -144px; -} - -.icon-hand-down { - background-position: -216px -144px; -} - -.icon-circle-arrow-right { - background-position: -240px -144px; -} - -.icon-circle-arrow-left { - background-position: -264px -144px; -} - -.icon-circle-arrow-up { - background-position: -288px -144px; -} - -.icon-circle-arrow-down { - background-position: -312px -144px; -} - -.icon-globe { - background-position: -336px -144px; -} - -.icon-wrench { - background-position: -360px -144px; -} - -.icon-tasks { - background-position: -384px -144px; -} - -.icon-filter { - background-position: -408px -144px; -} - -.icon-briefcase { - background-position: -432px -144px; -} - -.icon-fullscreen { - background-position: -456px -144px; -} - -.dropup, -.dropdown { - position: relative; -} - -.dropdown-toggle { - *margin-bottom: -3px; -} - -.dropdown-toggle:active, -.open .dropdown-toggle { - outline: 0; -} - -.caret { - display: inline-block; - width: 0; - height: 0; - vertical-align: top; - border-top: 4px solid #000000; - border-right: 4px solid transparent; - border-left: 4px solid transparent; - content: ""; -} - -.dropdown .caret { - margin-top: 8px; - margin-left: 2px; -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: 1000; - display: none; - float: left; - min-width: 160px; - padding: 5px 0; - margin: 2px 0 0; - list-style: none; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - *border-right-width: 2px; - *border-bottom-width: 2px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.dropdown-menu .divider { - *width: 100%; - height: 1px; - margin: 9px 1px; - *margin: -5px 0 5px; - overflow: hidden; - background-color: #e5e5e5; - border-bottom: 1px solid #ffffff; -} - -.dropdown-menu li > a { - display: block; - padding: 3px 20px; - clear: both; - font-weight: normal; - line-height: 20px; - color: #333333; - white-space: nowrap; -} - -.dropdown-menu li > a:hover, -.dropdown-menu li > a:focus, -.dropdown-submenu:hover > a { - color: #ffffff; - text-decoration: none; - background-color: #0081c2; - background-image: -moz-linear-gradient(top, #0088cc, #0077b3); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); - background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); - background-image: -o-linear-gradient(top, #0088cc, #0077b3); - background-image: linear-gradient(to bottom, #0088cc, #0077b3); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); -} - -.dropdown-menu .active > a, -.dropdown-menu .active > a:hover { - color: #ffffff; - text-decoration: none; - background-color: #0081c2; - background-image: -moz-linear-gradient(top, #0088cc, #0077b3); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); - background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); - background-image: -o-linear-gradient(top, #0088cc, #0077b3); - background-image: linear-gradient(to bottom, #0088cc, #0077b3); - background-repeat: repeat-x; - outline: 0; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0); -} - -.dropdown-menu .disabled > a, -.dropdown-menu .disabled > a:hover { - color: #999999; -} - -.dropdown-menu .disabled > a:hover { - text-decoration: none; - cursor: default; - background-color: transparent; - background-image: none; - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.open { - *z-index: 1000; -} - -.open > .dropdown-menu { - display: block; -} - -.pull-right > .dropdown-menu { - right: 0; - left: auto; -} - -.dropup .caret, -.navbar-fixed-bottom .dropdown .caret { - border-top: 0; - border-bottom: 4px solid #000000; - content: ""; -} - -.dropup .dropdown-menu, -.navbar-fixed-bottom .dropdown .dropdown-menu { - top: auto; - bottom: 100%; - margin-bottom: 1px; -} - -.dropdown-submenu { - position: relative; -} - -.dropdown-submenu > .dropdown-menu { - top: 0; - left: 100%; - margin-top: -6px; - margin-left: -1px; - -webkit-border-radius: 0 6px 6px 6px; - -moz-border-radius: 0 6px 6px 6px; - border-radius: 0 6px 6px 6px; -} - -.dropdown-submenu:hover > .dropdown-menu { - display: block; -} - -.dropup .dropdown-submenu > .dropdown-menu { - top: auto; - bottom: 0; - margin-top: 0; - margin-bottom: -2px; - -webkit-border-radius: 5px 5px 5px 0; - -moz-border-radius: 5px 5px 5px 0; - border-radius: 5px 5px 5px 0; -} - -.dropdown-submenu > a:after { - display: block; - float: right; - width: 0; - height: 0; - margin-top: 5px; - margin-right: -10px; - border-color: transparent; - border-left-color: #cccccc; - border-style: solid; - border-width: 5px 0 5px 5px; - content: " "; -} - -.dropdown-submenu:hover > a:after { - border-left-color: #ffffff; -} - -.dropdown-submenu.pull-left { - float: none; -} - -.dropdown-submenu.pull-left > .dropdown-menu { - left: -100%; - margin-left: 10px; - -webkit-border-radius: 6px 0 6px 6px; - -moz-border-radius: 6px 0 6px 6px; - border-radius: 6px 0 6px 6px; -} - -.dropdown .dropdown-menu .nav-header { - padding-right: 20px; - padding-left: 20px; -} - -.typeahead { - z-index: 1051; - margin-top: 2px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.well { - min-height: 20px; - padding: 19px; - margin-bottom: 20px; - background-color: #f5f5f5; - border: 1px solid #e3e3e3; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); -} - -.well blockquote { - border-color: #ddd; - border-color: rgba(0, 0, 0, 0.15); -} - -.well-large { - padding: 24px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.well-small { - padding: 9px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.fade { - opacity: 0; - -webkit-transition: opacity 0.15s linear; - -moz-transition: opacity 0.15s linear; - -o-transition: opacity 0.15s linear; - transition: opacity 0.15s linear; -} - -.fade.in { - opacity: 1; -} - -.collapse { - position: relative; - height: 0; - overflow: hidden; - -webkit-transition: height 0.35s ease; - -moz-transition: height 0.35s ease; - -o-transition: height 0.35s ease; - transition: height 0.35s ease; -} - -.collapse.in { - height: auto; -} - -.close { - float: right; - font-size: 20px; - font-weight: bold; - line-height: 20px; - color: #000000; - text-shadow: 0 1px 0 #ffffff; - opacity: 0.2; - filter: alpha(opacity=20); -} - -.close:hover { - color: #000000; - text-decoration: none; - cursor: pointer; - opacity: 0.4; - filter: alpha(opacity=40); -} - -button.close { - padding: 0; - cursor: pointer; - background: transparent; - border: 0; - -webkit-appearance: none; -} - -.btn { - display: inline-block; - *display: inline; - padding: 4px 12px; - margin-bottom: 0; - *margin-left: .3em; - font-size: 14px; - line-height: 20px; - color: #333333; - text-align: center; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - vertical-align: middle; - cursor: pointer; - background-color: #f5f5f5; - *background-color: #e6e6e6; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); - background-repeat: repeat-x; - border: 1px solid #bbbbbb; - *border: 0; - border-color: #e6e6e6 #e6e6e6 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - border-bottom-color: #a2a2a2; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - *zoom: 1; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn:hover, -.btn:active, -.btn.active, -.btn.disabled, -.btn[disabled] { - color: #333333; - background-color: #e6e6e6; - *background-color: #d9d9d9; -} - -.btn:active, -.btn.active { - background-color: #cccccc \9; -} - -.btn:first-child { - *margin-left: 0; -} - -.btn:hover { - color: #333333; - text-decoration: none; - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; -} - -.btn:focus { - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; -} - -.btn.active, -.btn:active { - background-image: none; - outline: 0; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn.disabled, -.btn[disabled] { - cursor: default; - background-image: none; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -.btn-large { - padding: 11px 19px; - font-size: 17.5px; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.btn-large [class^="icon-"], -.btn-large [class*=" icon-"] { - margin-top: 4px; -} - -.btn-small { - padding: 2px 10px; - font-size: 11.9px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.btn-small [class^="icon-"], -.btn-small [class*=" icon-"] { - margin-top: 0; -} - -.btn-mini [class^="icon-"], -.btn-mini [class*=" icon-"] { - margin-top: -1px; -} - -.btn-mini { - padding: 0 6px; - font-size: 10.5px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.btn-block { - display: block; - width: 100%; - padding-right: 0; - padding-left: 0; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -.btn-block + .btn-block { - margin-top: 5px; -} - -input[type="submit"].btn-block, -input[type="reset"].btn-block, -input[type="button"].btn-block { - width: 100%; -} - -.btn-primary.active, -.btn-warning.active, -.btn-danger.active, -.btn-success.active, -.btn-info.active, -.btn-inverse.active { - color: rgba(255, 255, 255, 0.75); -} - -.btn { - border-color: #c5c5c5; - border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25); -} - -.btn-primary { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #006dcc; - *background-color: #0044cc; - background-image: -moz-linear-gradient(top, #0088cc, #0044cc); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); - background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); - background-image: -o-linear-gradient(top, #0088cc, #0044cc); - background-image: linear-gradient(to bottom, #0088cc, #0044cc); - background-repeat: repeat-x; - border-color: #0044cc #0044cc #002a80; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-primary:hover, -.btn-primary:active, -.btn-primary.active, -.btn-primary.disabled, -.btn-primary[disabled] { - color: #ffffff; - background-color: #0044cc; - *background-color: #003bb3; -} - -.btn-primary:active, -.btn-primary.active { - background-color: #003399 \9; -} - -.btn-warning { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #faa732; - *background-color: #f89406; - background-image: -moz-linear-gradient(top, #fbb450, #f89406); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); - background-image: -webkit-linear-gradient(top, #fbb450, #f89406); - background-image: -o-linear-gradient(top, #fbb450, #f89406); - background-image: linear-gradient(to bottom, #fbb450, #f89406); - background-repeat: repeat-x; - border-color: #f89406 #f89406 #ad6704; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-warning:hover, -.btn-warning:active, -.btn-warning.active, -.btn-warning.disabled, -.btn-warning[disabled] { - color: #ffffff; - background-color: #f89406; - *background-color: #df8505; -} - -.btn-warning:active, -.btn-warning.active { - background-color: #c67605 \9; -} - -.btn-danger { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #da4f49; - *background-color: #bd362f; - background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); - background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); - background-image: linear-gradient(to bottom, #ee5f5b, #bd362f); - background-repeat: repeat-x; - border-color: #bd362f #bd362f #802420; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-danger:hover, -.btn-danger:active, -.btn-danger.active, -.btn-danger.disabled, -.btn-danger[disabled] { - color: #ffffff; - background-color: #bd362f; - *background-color: #a9302a; -} - -.btn-danger:active, -.btn-danger.active { - background-color: #942a25 \9; -} - -.btn-success { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #5bb75b; - *background-color: #51a351; - background-image: -moz-linear-gradient(top, #62c462, #51a351); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351)); - background-image: -webkit-linear-gradient(top, #62c462, #51a351); - background-image: -o-linear-gradient(top, #62c462, #51a351); - background-image: linear-gradient(to bottom, #62c462, #51a351); - background-repeat: repeat-x; - border-color: #51a351 #51a351 #387038; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-success:hover, -.btn-success:active, -.btn-success.active, -.btn-success.disabled, -.btn-success[disabled] { - color: #ffffff; - background-color: #51a351; - *background-color: #499249; -} - -.btn-success:active, -.btn-success.active { - background-color: #408140 \9; -} - -.btn-info { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #49afcd; - *background-color: #2f96b4; - background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4)); - background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4); - background-image: -o-linear-gradient(top, #5bc0de, #2f96b4); - background-image: linear-gradient(to bottom, #5bc0de, #2f96b4); - background-repeat: repeat-x; - border-color: #2f96b4 #2f96b4 #1f6377; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-info:hover, -.btn-info:active, -.btn-info.active, -.btn-info.disabled, -.btn-info[disabled] { - color: #ffffff; - background-color: #2f96b4; - *background-color: #2a85a0; -} - -.btn-info:active, -.btn-info.active { - background-color: #24748c \9; -} - -.btn-inverse { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #363636; - *background-color: #222222; - background-image: -moz-linear-gradient(top, #444444, #222222); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222)); - background-image: -webkit-linear-gradient(top, #444444, #222222); - background-image: -o-linear-gradient(top, #444444, #222222); - background-image: linear-gradient(to bottom, #444444, #222222); - background-repeat: repeat-x; - border-color: #222222 #222222 #000000; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.btn-inverse:hover, -.btn-inverse:active, -.btn-inverse.active, -.btn-inverse.disabled, -.btn-inverse[disabled] { - color: #ffffff; - background-color: #222222; - *background-color: #151515; -} - -.btn-inverse:active, -.btn-inverse.active { - background-color: #080808 \9; -} - -button.btn, -input[type="submit"].btn { - *padding-top: 3px; - *padding-bottom: 3px; -} - -button.btn::-moz-focus-inner, -input[type="submit"].btn::-moz-focus-inner { - padding: 0; - border: 0; -} - -button.btn.btn-large, -input[type="submit"].btn.btn-large { - *padding-top: 7px; - *padding-bottom: 7px; -} - -button.btn.btn-small, -input[type="submit"].btn.btn-small { - *padding-top: 3px; - *padding-bottom: 3px; -} - -button.btn.btn-mini, -input[type="submit"].btn.btn-mini { - *padding-top: 1px; - *padding-bottom: 1px; -} - -.btn-link, -.btn-link:active, -.btn-link[disabled] { - background-color: transparent; - background-image: none; - -webkit-box-shadow: none; - -moz-box-shadow: none; - box-shadow: none; -} - -.btn-link { - color: #0088cc; - cursor: pointer; - border-color: transparent; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-link:hover { - color: #005580; - text-decoration: underline; - background-color: transparent; -} - -.btn-link[disabled]:hover { - color: #333333; - text-decoration: none; -} - -.btn-group { - position: relative; - display: inline-block; - *display: inline; - *margin-left: .3em; - font-size: 0; - white-space: nowrap; - vertical-align: middle; - *zoom: 1; -} - -.btn-group:first-child { - *margin-left: 0; -} - -.btn-group + .btn-group { - margin-left: 5px; -} - -.btn-toolbar { - margin-top: 10px; - margin-bottom: 10px; - font-size: 0; -} - -.btn-toolbar > .btn + .btn, -.btn-toolbar > .btn-group + .btn, -.btn-toolbar > .btn + .btn-group { - margin-left: 5px; -} - -.btn-group > .btn { - position: relative; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-group > .btn + .btn { - margin-left: -1px; -} - -.btn-group > .btn, -.btn-group > .dropdown-menu, -.btn-group > .popover { - font-size: 14px; -} - -.btn-group > .btn-mini { - font-size: 10.5px; -} - -.btn-group > .btn-small { - font-size: 11.9px; -} - -.btn-group > .btn-large { - font-size: 17.5px; -} - -.btn-group > .btn:first-child { - margin-left: 0; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 4px; -} - -.btn-group > .btn:last-child, -.btn-group > .dropdown-toggle { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; -} - -.btn-group > .btn.large:first-child { - margin-left: 0; - -webkit-border-bottom-left-radius: 6px; - border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; -} - -.btn-group > .btn.large:last-child, -.btn-group > .large.dropdown-toggle { - -webkit-border-top-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-topright: 6px; - -moz-border-radius-bottomright: 6px; -} - -.btn-group > .btn:hover, -.btn-group > .btn:focus, -.btn-group > .btn:active, -.btn-group > .btn.active { - z-index: 2; -} - -.btn-group .dropdown-toggle:active, -.btn-group.open .dropdown-toggle { - outline: 0; -} - -.btn-group > .btn + .dropdown-toggle { - *padding-top: 5px; - padding-right: 8px; - *padding-bottom: 5px; - padding-left: 8px; - -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group > .btn-mini + .dropdown-toggle { - *padding-top: 2px; - padding-right: 5px; - *padding-bottom: 2px; - padding-left: 5px; -} - -.btn-group > .btn-small + .dropdown-toggle { - *padding-top: 5px; - *padding-bottom: 4px; -} - -.btn-group > .btn-large + .dropdown-toggle { - *padding-top: 7px; - padding-right: 12px; - *padding-bottom: 7px; - padding-left: 12px; -} - -.btn-group.open .dropdown-toggle { - background-image: none; - -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.btn-group.open .btn.dropdown-toggle { - background-color: #e6e6e6; -} - -.btn-group.open .btn-primary.dropdown-toggle { - background-color: #0044cc; -} - -.btn-group.open .btn-warning.dropdown-toggle { - background-color: #f89406; -} - -.btn-group.open .btn-danger.dropdown-toggle { - background-color: #bd362f; -} - -.btn-group.open .btn-success.dropdown-toggle { - background-color: #51a351; -} - -.btn-group.open .btn-info.dropdown-toggle { - background-color: #2f96b4; -} - -.btn-group.open .btn-inverse.dropdown-toggle { - background-color: #222222; -} - -.btn .caret { - margin-top: 8px; - margin-left: 0; -} - -.btn-mini .caret, -.btn-small .caret, -.btn-large .caret { - margin-top: 6px; -} - -.btn-large .caret { - border-top-width: 5px; - border-right-width: 5px; - border-left-width: 5px; -} - -.dropup .btn-large .caret { - border-bottom-width: 5px; -} - -.btn-primary .caret, -.btn-warning .caret, -.btn-danger .caret, -.btn-info .caret, -.btn-success .caret, -.btn-inverse .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.btn-group-vertical { - display: inline-block; - *display: inline; - /* IE7 inline-block hack */ - - *zoom: 1; -} - -.btn-group-vertical > .btn { - display: block; - float: none; - max-width: 100%; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.btn-group-vertical > .btn + .btn { - margin-top: -1px; - margin-left: 0; -} - -.btn-group-vertical > .btn:first-child { - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} - -.btn-group-vertical > .btn:last-child { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.btn-group-vertical > .btn-large:first-child { - -webkit-border-radius: 6px 6px 0 0; - -moz-border-radius: 6px 6px 0 0; - border-radius: 6px 6px 0 0; -} - -.btn-group-vertical > .btn-large:last-child { - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; -} - -.alert { - padding: 8px 35px 8px 14px; - margin-bottom: 20px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - background-color: #fcf8e3; - border: 1px solid #fbeed5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.alert, -.alert h4 { - color: #c09853; -} - -.alert h4 { - margin: 0; -} - -.alert .close { - position: relative; - top: -2px; - right: -21px; - line-height: 20px; -} - -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.alert-success h4 { - color: #468847; -} - -.alert-danger, -.alert-error { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; -} - -.alert-danger h4, -.alert-error h4 { - color: #b94a48; -} - -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.alert-info h4 { - color: #3a87ad; -} - -.alert-block { - padding-top: 14px; - padding-bottom: 14px; -} - -.alert-block > p, -.alert-block > ul { - margin-bottom: 0; -} - -.alert-block p + p { - margin-top: 5px; -} - -.nav { - margin-bottom: 20px; - margin-left: 0; - list-style: none; -} - -.nav > li > a { - display: block; -} - -.nav > li > a:hover { - text-decoration: none; - background-color: #eeeeee; -} - -.nav > li > a > img { - max-width: none; -} - -.nav > .pull-right { - float: right; -} - -.nav-header { - display: block; - padding: 3px 15px; - font-size: 11px; - font-weight: bold; - line-height: 20px; - color: #999999; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); - text-transform: uppercase; -} - -.nav li + .nav-header { - margin-top: 9px; -} - -.nav-list { - padding-right: 15px; - padding-left: 15px; - margin-bottom: 0; -} - -.nav-list > li > a, -.nav-list .nav-header { - margin-right: -15px; - margin-left: -15px; - text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); -} - -.nav-list > li > a { - padding: 3px 15px; -} - -.nav-list > .active > a, -.nav-list > .active > a:hover { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); - background-color: #0088cc; -} - -.nav-list [class^="icon-"], -.nav-list [class*=" icon-"] { - margin-right: 2px; -} - -.nav-list .divider { - *width: 100%; - height: 1px; - margin: 9px 1px; - *margin: -5px 0 5px; - overflow: hidden; - background-color: #e5e5e5; - border-bottom: 1px solid #ffffff; -} - -.nav-tabs, -.nav-pills { - *zoom: 1; -} - -.nav-tabs:before, -.nav-pills:before, -.nav-tabs:after, -.nav-pills:after { - display: table; - line-height: 0; - content: ""; -} - -.nav-tabs:after, -.nav-pills:after { - clear: both; -} - -.nav-tabs > li, -.nav-pills > li { - float: left; -} - -.nav-tabs > li > a, -.nav-pills > li > a { - padding-right: 12px; - padding-left: 12px; - margin-right: 2px; - line-height: 14px; -} - -.nav-tabs { - border-bottom: 1px solid #ddd; -} - -.nav-tabs > li { - margin-bottom: -1px; -} - -.nav-tabs > li > a { - padding-top: 8px; - padding-bottom: 8px; - line-height: 20px; - border: 1px solid transparent; - -webkit-border-radius: 4px 4px 0 0; - -moz-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; -} - -.nav-tabs > li > a:hover { - border-color: #eeeeee #eeeeee #dddddd; -} - -.nav-tabs > .active > a, -.nav-tabs > .active > a:hover { - color: #555555; - cursor: default; - background-color: #ffffff; - border: 1px solid #ddd; - border-bottom-color: transparent; -} - -.nav-pills > li > a { - padding-top: 8px; - padding-bottom: 8px; - margin-top: 2px; - margin-bottom: 2px; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} - -.nav-pills > .active > a, -.nav-pills > .active > a:hover { - color: #ffffff; - background-color: #0088cc; -} - -.nav-stacked > li { - float: none; -} - -.nav-stacked > li > a { - margin-right: 0; -} - -.nav-tabs.nav-stacked { - border-bottom: 0; -} - -.nav-tabs.nav-stacked > li > a { - border: 1px solid #ddd; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.nav-tabs.nav-stacked > li:first-child > a { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-topleft: 4px; -} - -.nav-tabs.nav-stacked > li:last-child > a { - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -moz-border-radius-bottomright: 4px; - -moz-border-radius-bottomleft: 4px; -} - -.nav-tabs.nav-stacked > li > a:hover { - z-index: 2; - border-color: #ddd; -} - -.nav-pills.nav-stacked > li > a { - margin-bottom: 3px; -} - -.nav-pills.nav-stacked > li:last-child > a { - margin-bottom: 1px; -} - -.nav-tabs .dropdown-menu { - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; -} - -.nav-pills .dropdown-menu { - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.nav .dropdown-toggle .caret { - margin-top: 6px; - border-top-color: #0088cc; - border-bottom-color: #0088cc; -} - -.nav .dropdown-toggle:hover .caret { - border-top-color: #005580; - border-bottom-color: #005580; -} - -/* move down carets for tabs */ - -.nav-tabs .dropdown-toggle .caret { - margin-top: 8px; -} - -.nav .active .dropdown-toggle .caret { - border-top-color: #fff; - border-bottom-color: #fff; -} - -.nav-tabs .active .dropdown-toggle .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.nav > .dropdown.active > a:hover { - cursor: pointer; -} - -.nav-tabs .open .dropdown-toggle, -.nav-pills .open .dropdown-toggle, -.nav > li.dropdown.open.active > a:hover { - color: #ffffff; - background-color: #999999; - border-color: #999999; -} - -.nav li.dropdown.open .caret, -.nav li.dropdown.open.active .caret, -.nav li.dropdown.open a:hover .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; - opacity: 1; - filter: alpha(opacity=100); -} - -.tabs-stacked .open > a:hover { - border-color: #999999; -} - -.tabbable { - *zoom: 1; -} - -.tabbable:before, -.tabbable:after { - display: table; - line-height: 0; - content: ""; -} - -.tabbable:after { - clear: both; -} - -.tab-content { - overflow: auto; -} - -.tabs-below > .nav-tabs, -.tabs-right > .nav-tabs, -.tabs-left > .nav-tabs { - border-bottom: 0; -} - -.tab-content > .tab-pane, -.pill-content > .pill-pane { - display: none; -} - -.tab-content > .active, -.pill-content > .active { - display: block; -} - -.tabs-below > .nav-tabs { - border-top: 1px solid #ddd; -} - -.tabs-below > .nav-tabs > li { - margin-top: -1px; - margin-bottom: 0; -} - -.tabs-below > .nav-tabs > li > a { - -webkit-border-radius: 0 0 4px 4px; - -moz-border-radius: 0 0 4px 4px; - border-radius: 0 0 4px 4px; -} - -.tabs-below > .nav-tabs > li > a:hover { - border-top-color: #ddd; - border-bottom-color: transparent; -} - -.tabs-below > .nav-tabs > .active > a, -.tabs-below > .nav-tabs > .active > a:hover { - border-color: transparent #ddd #ddd #ddd; -} - -.tabs-left > .nav-tabs > li, -.tabs-right > .nav-tabs > li { - float: none; -} - -.tabs-left > .nav-tabs > li > a, -.tabs-right > .nav-tabs > li > a { - min-width: 74px; - margin-right: 0; - margin-bottom: 3px; -} - -.tabs-left > .nav-tabs { - float: left; - margin-right: 19px; - border-right: 1px solid #ddd; -} - -.tabs-left > .nav-tabs > li > a { - margin-right: -1px; - -webkit-border-radius: 4px 0 0 4px; - -moz-border-radius: 4px 0 0 4px; - border-radius: 4px 0 0 4px; -} - -.tabs-left > .nav-tabs > li > a:hover { - border-color: #eeeeee #dddddd #eeeeee #eeeeee; -} - -.tabs-left > .nav-tabs .active > a, -.tabs-left > .nav-tabs .active > a:hover { - border-color: #ddd transparent #ddd #ddd; - *border-right-color: #ffffff; -} - -.tabs-right > .nav-tabs { - float: right; - margin-left: 19px; - border-left: 1px solid #ddd; -} - -.tabs-right > .nav-tabs > li > a { - margin-left: -1px; - -webkit-border-radius: 0 4px 4px 0; - -moz-border-radius: 0 4px 4px 0; - border-radius: 0 4px 4px 0; -} - -.tabs-right > .nav-tabs > li > a:hover { - border-color: #eeeeee #eeeeee #eeeeee #dddddd; -} - -.tabs-right > .nav-tabs .active > a, -.tabs-right > .nav-tabs .active > a:hover { - border-color: #ddd #ddd #ddd transparent; - *border-left-color: #ffffff; -} - -.nav > .disabled > a { - color: #999999; -} - -.nav > .disabled > a:hover { - text-decoration: none; - cursor: default; - background-color: transparent; -} - -.navbar { - *position: relative; - *z-index: 2; - margin-bottom: 20px; - overflow: visible; -} - -.navbar-inner { - min-height: 40px; - padding-right: 20px; - padding-left: 20px; - background-color: #fafafa; - background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2)); - background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2); - background-image: -o-linear-gradient(top, #ffffff, #f2f2f2); - background-image: linear-gradient(to bottom, #ffffff, #f2f2f2); - background-repeat: repeat-x; - border: 1px solid #d4d4d4; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0); - *zoom: 1; - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065); -} - -.navbar-inner:before, -.navbar-inner:after { - display: table; - line-height: 0; - content: ""; -} - -.navbar-inner:after { - clear: both; -} - -.navbar .container { - width: auto; -} - -.nav-collapse.collapse { - height: auto; - overflow: visible; -} - -.navbar .brand { - display: block; - float: left; - padding: 10px 20px 10px; - margin-left: -20px; - font-size: 20px; - font-weight: 200; - color: #777777; - text-shadow: 0 1px 0 #ffffff; -} - -.navbar .brand:hover { - text-decoration: none; -} - -.navbar-text { - margin-bottom: 0; - line-height: 40px; - color: #777777; -} - -.navbar-link { - color: #777777; -} - -.navbar-link:hover { - color: #333333; -} - -.navbar .divider-vertical { - height: 40px; - margin: 0 9px; - border-right: 1px solid #ffffff; - border-left: 1px solid #f2f2f2; -} - -.navbar .btn, -.navbar .btn-group { - margin-top: 5px; -} - -.navbar .btn-group .btn, -.navbar .input-prepend .btn, -.navbar .input-append .btn { - margin-top: 0; -} - -.navbar-form { - margin-bottom: 0; - *zoom: 1; -} - -.navbar-form:before, -.navbar-form:after { - display: table; - line-height: 0; - content: ""; -} - -.navbar-form:after { - clear: both; -} - -.navbar-form input, -.navbar-form select, -.navbar-form .radio, -.navbar-form .checkbox { - margin-top: 5px; -} - -.navbar-form input, -.navbar-form select, -.navbar-form .btn { - display: inline-block; - margin-bottom: 0; -} - -.navbar-form input[type="image"], -.navbar-form input[type="checkbox"], -.navbar-form input[type="radio"] { - margin-top: 3px; -} - -.navbar-form .input-append, -.navbar-form .input-prepend { - margin-top: 5px; - white-space: nowrap; -} - -.navbar-form .input-append input, -.navbar-form .input-prepend input { - margin-top: 0; -} - -.navbar-search { - position: relative; - float: left; - margin-top: 5px; - margin-bottom: 0; -} - -.navbar-search .search-query { - padding: 4px 14px; - margin-bottom: 0; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 13px; - font-weight: normal; - line-height: 1; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.navbar-static-top { - position: static; - margin-bottom: 0; -} - -.navbar-static-top .navbar-inner { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.navbar-fixed-top, -.navbar-fixed-bottom { - position: fixed; - right: 0; - left: 0; - z-index: 1030; - margin-bottom: 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - border-width: 0 0 1px; -} - -.navbar-fixed-bottom .navbar-inner { - border-width: 1px 0 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-fixed-bottom .navbar-inner { - padding-right: 0; - padding-left: 0; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; -} - -.navbar-static-top .container, -.navbar-fixed-top .container, -.navbar-fixed-bottom .container { - width: 940px; -} - -.navbar-fixed-top { - top: 0; -} - -.navbar-fixed-top .navbar-inner, -.navbar-static-top .navbar-inner { - -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); - box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1); -} - -.navbar-fixed-bottom { - bottom: 0; -} - -.navbar-fixed-bottom .navbar-inner { - -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); - -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); - box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); -} - -.navbar .nav { - position: relative; - left: 0; - display: block; - float: left; - margin: 0 10px 0 0; -} - -.navbar .nav.pull-right { - float: right; - margin-right: 0; -} - -.navbar .nav > li { - float: left; -} - -.navbar .nav > li > a { - float: none; - padding: 10px 15px 10px; - color: #777777; - text-decoration: none; - text-shadow: 0 1px 0 #ffffff; -} - -.navbar .nav .dropdown-toggle .caret { - margin-top: 8px; -} - -.navbar .nav > li > a:focus, -.navbar .nav > li > a:hover { - color: #333333; - text-decoration: none; - background-color: transparent; -} - -.navbar .nav > .active > a, -.navbar .nav > .active > a:hover, -.navbar .nav > .active > a:focus { - color: #555555; - text-decoration: none; - background-color: #e5e5e5; - -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); - -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125); -} - -.navbar .btn-navbar { - display: none; - float: right; - padding: 7px 10px; - margin-right: 5px; - margin-left: 5px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #ededed; - *background-color: #e5e5e5; - background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5)); - background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5); - background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5); - background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5); - background-repeat: repeat-x; - border-color: #e5e5e5 #e5e5e5 #bfbfbf; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075); -} - -.navbar .btn-navbar:hover, -.navbar .btn-navbar:active, -.navbar .btn-navbar.active, -.navbar .btn-navbar.disabled, -.navbar .btn-navbar[disabled] { - color: #ffffff; - background-color: #e5e5e5; - *background-color: #d9d9d9; -} - -.navbar .btn-navbar:active, -.navbar .btn-navbar.active { - background-color: #cccccc \9; -} - -.navbar .btn-navbar .icon-bar { - display: block; - width: 18px; - height: 2px; - background-color: #f5f5f5; - -webkit-border-radius: 1px; - -moz-border-radius: 1px; - border-radius: 1px; - -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); -} - -.btn-navbar .icon-bar + .icon-bar { - margin-top: 3px; -} - -.navbar .nav > li > .dropdown-menu:before { - position: absolute; - top: -7px; - left: 9px; - display: inline-block; - border-right: 7px solid transparent; - border-bottom: 7px solid #ccc; - border-left: 7px solid transparent; - border-bottom-color: rgba(0, 0, 0, 0.2); - content: ''; -} - -.navbar .nav > li > .dropdown-menu:after { - position: absolute; - top: -6px; - left: 10px; - display: inline-block; - border-right: 6px solid transparent; - border-bottom: 6px solid #ffffff; - border-left: 6px solid transparent; - content: ''; -} - -.navbar-fixed-bottom .nav > li > .dropdown-menu:before { - top: auto; - bottom: -7px; - border-top: 7px solid #ccc; - border-bottom: 0; - border-top-color: rgba(0, 0, 0, 0.2); -} - -.navbar-fixed-bottom .nav > li > .dropdown-menu:after { - top: auto; - bottom: -6px; - border-top: 6px solid #ffffff; - border-bottom: 0; -} - -.navbar .nav li.dropdown > a:hover .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.navbar .nav li.dropdown.open > .dropdown-toggle, -.navbar .nav li.dropdown.active > .dropdown-toggle, -.navbar .nav li.dropdown.open.active > .dropdown-toggle { - color: #555555; - background-color: #e5e5e5; -} - -.navbar .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: #777777; - border-bottom-color: #777777; -} - -.navbar .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: #555555; - border-bottom-color: #555555; -} - -.navbar .pull-right > li > .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right { - right: 0; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu:before, -.navbar .nav > li > .dropdown-menu.pull-right:before { - right: 12px; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu:after, -.navbar .nav > li > .dropdown-menu.pull-right:after { - right: 13px; - left: auto; -} - -.navbar .pull-right > li > .dropdown-menu .dropdown-menu, -.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu { - right: 100%; - left: auto; - margin-right: -1px; - margin-left: 0; - -webkit-border-radius: 6px 0 6px 6px; - -moz-border-radius: 6px 0 6px 6px; - border-radius: 6px 0 6px 6px; -} - -.navbar-inverse .navbar-inner { - background-color: #1b1b1b; - background-image: -moz-linear-gradient(top, #222222, #111111); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111)); - background-image: -webkit-linear-gradient(top, #222222, #111111); - background-image: -o-linear-gradient(top, #222222, #111111); - background-image: linear-gradient(to bottom, #222222, #111111); - background-repeat: repeat-x; - border-color: #252525; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0); -} - -.navbar-inverse .brand, -.navbar-inverse .nav > li > a { - color: #999999; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); -} - -.navbar-inverse .brand:hover, -.navbar-inverse .nav > li > a:hover { - color: #ffffff; -} - -.navbar-inverse .brand { - color: #999999; -} - -.navbar-inverse .navbar-text { - color: #999999; -} - -.navbar-inverse .nav > li > a:focus, -.navbar-inverse .nav > li > a:hover { - color: #ffffff; - background-color: transparent; -} - -.navbar-inverse .nav .active > a, -.navbar-inverse .nav .active > a:hover, -.navbar-inverse .nav .active > a:focus { - color: #ffffff; - background-color: #111111; -} - -.navbar-inverse .navbar-link { - color: #999999; -} - -.navbar-inverse .navbar-link:hover { - color: #ffffff; -} - -.navbar-inverse .divider-vertical { - border-right-color: #222222; - border-left-color: #111111; -} - -.navbar-inverse .nav li.dropdown.open > .dropdown-toggle, -.navbar-inverse .nav li.dropdown.active > .dropdown-toggle, -.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle { - color: #ffffff; - background-color: #111111; -} - -.navbar-inverse .nav li.dropdown > a:hover .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret { - border-top-color: #999999; - border-bottom-color: #999999; -} - -.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret, -.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret, -.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret { - border-top-color: #ffffff; - border-bottom-color: #ffffff; -} - -.navbar-inverse .navbar-search .search-query { - color: #ffffff; - background-color: #515151; - border-color: #111111; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15); - -webkit-transition: none; - -moz-transition: none; - -o-transition: none; - transition: none; -} - -.navbar-inverse .navbar-search .search-query:-moz-placeholder { - color: #cccccc; -} - -.navbar-inverse .navbar-search .search-query:-ms-input-placeholder { - color: #cccccc; -} - -.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder { - color: #cccccc; -} - -.navbar-inverse .navbar-search .search-query:focus, -.navbar-inverse .navbar-search .search-query.focused { - padding: 5px 15px; - color: #333333; - text-shadow: 0 1px 0 #ffffff; - background-color: #ffffff; - border: 0; - outline: 0; - -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); - box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); -} - -.navbar-inverse .btn-navbar { - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #0e0e0e; - *background-color: #040404; - background-image: -moz-linear-gradient(top, #151515, #040404); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404)); - background-image: -webkit-linear-gradient(top, #151515, #040404); - background-image: -o-linear-gradient(top, #151515, #040404); - background-image: linear-gradient(to bottom, #151515, #040404); - background-repeat: repeat-x; - border-color: #040404 #040404 #000000; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); -} - -.navbar-inverse .btn-navbar:hover, -.navbar-inverse .btn-navbar:active, -.navbar-inverse .btn-navbar.active, -.navbar-inverse .btn-navbar.disabled, -.navbar-inverse .btn-navbar[disabled] { - color: #ffffff; - background-color: #040404; - *background-color: #000000; -} - -.navbar-inverse .btn-navbar:active, -.navbar-inverse .btn-navbar.active { - background-color: #000000 \9; -} - -.breadcrumb { - padding: 8px 15px; - margin: 0 0 20px; - list-style: none; - background-color: #f5f5f5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.breadcrumb > li { - display: inline-block; - *display: inline; - text-shadow: 0 1px 0 #ffffff; - *zoom: 1; -} - -.breadcrumb > li > .divider { - padding: 0 5px; - color: #ccc; -} - -.breadcrumb > .active { - color: #999999; -} - -.pagination { - margin: 20px 0; -} - -.pagination ul { - display: inline-block; - *display: inline; - margin-bottom: 0; - margin-left: 0; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - *zoom: 1; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.pagination ul > li { - display: inline; -} - -.pagination ul > li > a, -.pagination ul > li > span { - float: left; - padding: 4px 12px; - line-height: 20px; - text-decoration: none; - background-color: #ffffff; - border: 1px solid #dddddd; - border-left-width: 0; -} - -.pagination ul > li > a:hover, -.pagination ul > .active > a, -.pagination ul > .active > span { - background-color: #f5f5f5; -} - -.pagination ul > .active > a, -.pagination ul > .active > span { - color: #999999; - cursor: default; -} - -.pagination ul > .disabled > span, -.pagination ul > .disabled > a, -.pagination ul > .disabled > a:hover { - color: #999999; - cursor: default; - background-color: transparent; -} - -.pagination ul > li:first-child > a, -.pagination ul > li:first-child > span { - border-left-width: 1px; - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - -moz-border-radius-topleft: 4px; -} - -.pagination ul > li:last-child > a, -.pagination ul > li:last-child > span { - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; - -moz-border-radius-topright: 4px; - -moz-border-radius-bottomright: 4px; -} - -.pagination-centered { - text-align: center; -} - -.pagination-right { - text-align: right; -} - -.pagination-large ul > li > a, -.pagination-large ul > li > span { - padding: 11px 19px; - font-size: 17.5px; -} - -.pagination-large ul > li:first-child > a, -.pagination-large ul > li:first-child > span { - -webkit-border-bottom-left-radius: 6px; - border-bottom-left-radius: 6px; - -webkit-border-top-left-radius: 6px; - border-top-left-radius: 6px; - -moz-border-radius-bottomleft: 6px; - -moz-border-radius-topleft: 6px; -} - -.pagination-large ul > li:last-child > a, -.pagination-large ul > li:last-child > span { - -webkit-border-top-right-radius: 6px; - border-top-right-radius: 6px; - -webkit-border-bottom-right-radius: 6px; - border-bottom-right-radius: 6px; - -moz-border-radius-topright: 6px; - -moz-border-radius-bottomright: 6px; -} - -.pagination-mini ul > li:first-child > a, -.pagination-small ul > li:first-child > a, -.pagination-mini ul > li:first-child > span, -.pagination-small ul > li:first-child > span { - -webkit-border-bottom-left-radius: 3px; - border-bottom-left-radius: 3px; - -webkit-border-top-left-radius: 3px; - border-top-left-radius: 3px; - -moz-border-radius-bottomleft: 3px; - -moz-border-radius-topleft: 3px; -} - -.pagination-mini ul > li:last-child > a, -.pagination-small ul > li:last-child > a, -.pagination-mini ul > li:last-child > span, -.pagination-small ul > li:last-child > span { - -webkit-border-top-right-radius: 3px; - border-top-right-radius: 3px; - -webkit-border-bottom-right-radius: 3px; - border-bottom-right-radius: 3px; - -moz-border-radius-topright: 3px; - -moz-border-radius-bottomright: 3px; -} - -.pagination-small ul > li > a, -.pagination-small ul > li > span { - padding: 2px 10px; - font-size: 11.9px; -} - -.pagination-mini ul > li > a, -.pagination-mini ul > li > span { - padding: 0 6px; - font-size: 10.5px; -} - -.pager { - margin: 20px 0; - text-align: center; - list-style: none; - *zoom: 1; -} - -.pager:before, -.pager:after { - display: table; - line-height: 0; - content: ""; -} - -.pager:after { - clear: both; -} - -.pager li { - display: inline; -} - -.pager li > a, -.pager li > span { - display: inline-block; - padding: 5px 14px; - background-color: #fff; - border: 1px solid #ddd; - -webkit-border-radius: 15px; - -moz-border-radius: 15px; - border-radius: 15px; -} - -.pager li > a:hover { - text-decoration: none; - background-color: #f5f5f5; -} - -.pager .next > a, -.pager .next > span { - float: right; -} - -.pager .previous > a, -.pager .previous > span { - float: left; -} - -.pager .disabled > a, -.pager .disabled > a:hover, -.pager .disabled > span { - color: #999999; - cursor: default; - background-color: #fff; -} - -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: 1040; - background-color: #000000; -} - -.modal-backdrop.fade { - opacity: 0; -} - -.modal-backdrop, -.modal-backdrop.fade.in { - opacity: 0.8; - filter: alpha(opacity=80); -} - -.modal { - position: fixed; - top: 10%; - left: 50%; - z-index: 1050; - width: 560px; - margin-left: -280px; - background-color: #ffffff; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, 0.3); - *border: 1px solid #999; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - outline: none; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} - -.modal.fade { - top: -25%; - -webkit-transition: opacity 0.3s linear, top 0.3s ease-out; - -moz-transition: opacity 0.3s linear, top 0.3s ease-out; - -o-transition: opacity 0.3s linear, top 0.3s ease-out; - transition: opacity 0.3s linear, top 0.3s ease-out; -} - -.modal.fade.in { - top: 10%; -} - -.modal-header { - padding: 9px 15px; - border-bottom: 1px solid #eee; -} - -.modal-header .close { - margin-top: 2px; -} - -.modal-header h3 { - margin: 0; - line-height: 30px; -} - -.modal-body { - position: relative; - max-height: 400px; - padding: 15px; - overflow-y: auto; -} - -.modal-form { - margin-bottom: 0; -} - -.modal-footer { - padding: 14px 15px 15px; - margin-bottom: 0; - text-align: right; - background-color: #f5f5f5; - border-top: 1px solid #ddd; - -webkit-border-radius: 0 0 6px 6px; - -moz-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - *zoom: 1; - -webkit-box-shadow: inset 0 1px 0 #ffffff; - -moz-box-shadow: inset 0 1px 0 #ffffff; - box-shadow: inset 0 1px 0 #ffffff; -} - -.modal-footer:before, -.modal-footer:after { - display: table; - line-height: 0; - content: ""; -} - -.modal-footer:after { - clear: both; -} - -.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; -} - -.tooltip { - position: absolute; - z-index: 1030; - display: block; - padding: 5px; - font-size: 11px; - opacity: 0; - filter: alpha(opacity=0); - visibility: visible; -} - -.tooltip.in { - opacity: 0.8; - filter: alpha(opacity=80); -} - -.tooltip.top { - margin-top: -3px; -} - -.tooltip.right { - margin-left: 3px; -} - -.tooltip.bottom { - margin-top: 3px; -} - -.tooltip.left { - margin-left: -3px; -} - -.tooltip-inner { - max-width: 200px; - padding: 3px 8px; - color: #ffffff; - text-align: center; - text-decoration: none; - background-color: #000000; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - 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-top-color: #000000; - border-width: 5px 5px 0; -} - -.tooltip.right .tooltip-arrow { - top: 50%; - left: 0; - margin-top: -5px; - border-right-color: #000000; - border-width: 5px 5px 5px 0; -} - -.tooltip.left .tooltip-arrow { - top: 50%; - right: 0; - margin-top: -5px; - border-left-color: #000000; - border-width: 5px 0 5px 5px; -} - -.tooltip.bottom .tooltip-arrow { - top: 0; - left: 50%; - margin-left: -5px; - border-bottom-color: #000000; - border-width: 0 5px 5px; -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1010; - display: none; - width: 236px; - padding: 1px; - text-align: left; - white-space: normal; - background-color: #ffffff; - border: 1px solid #ccc; - border: 1px solid rgba(0, 0, 0, 0.2); - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; -} - -.popover.top { - margin-top: -10px; -} - -.popover.right { - margin-left: 10px; -} - -.popover.bottom { - margin-top: 10px; -} - -.popover.left { - margin-left: -10px; -} - -.popover-title { - padding: 8px 14px; - margin: 0; - font-size: 14px; - font-weight: normal; - line-height: 18px; - background-color: #f7f7f7; - border-bottom: 1px solid #ebebeb; - -webkit-border-radius: 5px 5px 0 0; - -moz-border-radius: 5px 5px 0 0; - border-radius: 5px 5px 0 0; -} - -.popover-content { - padding: 9px 14px; -} - -.popover .arrow, -.popover .arrow:after { - position: absolute; - display: block; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; -} - -.popover .arrow { - border-width: 11px; -} - -.popover .arrow:after { - border-width: 10px; - content: ""; -} - -.popover.top .arrow { - bottom: -11px; - left: 50%; - margin-left: -11px; - border-top-color: #999; - border-top-color: rgba(0, 0, 0, 0.25); - border-bottom-width: 0; -} - -.popover.top .arrow:after { - bottom: 1px; - margin-left: -10px; - border-top-color: #ffffff; - 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, 0.25); - border-left-width: 0; -} - -.popover.right .arrow:after { - bottom: -10px; - left: 1px; - border-right-color: #ffffff; - border-left-width: 0; -} - -.popover.bottom .arrow { - top: -11px; - left: 50%; - margin-left: -11px; - border-bottom-color: #999; - border-bottom-color: rgba(0, 0, 0, 0.25); - border-top-width: 0; -} - -.popover.bottom .arrow:after { - top: 1px; - margin-left: -10px; - border-bottom-color: #ffffff; - border-top-width: 0; -} - -.popover.left .arrow { - top: 50%; - right: -11px; - margin-top: -11px; - border-left-color: #999; - border-left-color: rgba(0, 0, 0, 0.25); - border-right-width: 0; -} - -.popover.left .arrow:after { - right: 1px; - bottom: -10px; - border-left-color: #ffffff; - border-right-width: 0; -} - -.thumbnails { - margin-left: -20px; - list-style: none; - *zoom: 1; -} - -.thumbnails:before, -.thumbnails:after { - display: table; - line-height: 0; - content: ""; -} - -.thumbnails:after { - clear: both; -} - -.row-fluid .thumbnails { - margin-left: 0; -} - -.thumbnails > li { - float: left; - margin-bottom: 20px; - margin-left: 20px; -} - -.thumbnail { - display: block; - padding: 4px; - line-height: 20px; - border: 1px solid #ddd; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055); - -webkit-transition: all 0.2s ease-in-out; - -moz-transition: all 0.2s ease-in-out; - -o-transition: all 0.2s ease-in-out; - transition: all 0.2s ease-in-out; -} - -a.thumbnail:hover { - border-color: #0088cc; - -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); - box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25); -} - -.thumbnail > img { - display: block; - max-width: 100%; - margin-right: auto; - margin-left: auto; -} - -.thumbnail .caption { - padding: 9px; - color: #555555; -} - -.media, -.media-body { - overflow: hidden; - *overflow: visible; - zoom: 1; -} - -.media, -.media .media { - margin-top: 15px; -} - -.media:first-child { - margin-top: 0; -} - -.media-object { - display: block; -} - -.media-heading { - margin: 0 0 5px; -} - -.media .pull-left { - margin-right: 10px; -} - -.media .pull-right { - margin-left: 10px; -} - -.media-list { - margin-left: 0; - list-style: none; -} - -.label, -.badge { - display: inline-block; - padding: 2px 4px; - font-size: 11.844px; - font-weight: bold; - line-height: 14px; - color: #ffffff; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - white-space: nowrap; - vertical-align: baseline; - background-color: #999999; -} - -.label { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; -} - -.badge { - padding-right: 9px; - padding-left: 9px; - -webkit-border-radius: 9px; - -moz-border-radius: 9px; - border-radius: 9px; -} - -.label:empty, -.badge:empty { - display: none; -} - -a.label:hover, -a.badge:hover { - color: #ffffff; - text-decoration: none; - cursor: pointer; -} - -.label-important, -.badge-important { - background-color: #b94a48; -} - -.label-important[href], -.badge-important[href] { - background-color: #953b39; -} - -.label-warning, -.badge-warning { - background-color: #f89406; -} - -.label-warning[href], -.badge-warning[href] { - background-color: #c67605; -} - -.label-success, -.badge-success { - background-color: #468847; -} - -.label-success[href], -.badge-success[href] { - background-color: #356635; -} - -.label-info, -.badge-info { - background-color: #3a87ad; -} - -.label-info[href], -.badge-info[href] { - background-color: #2d6987; -} - -.label-inverse, -.badge-inverse { - background-color: #333333; -} - -.label-inverse[href], -.badge-inverse[href] { - background-color: #1a1a1a; -} - -.btn .label, -.btn .badge { - position: relative; - top: -1px; -} - -.btn-mini .label, -.btn-mini .badge { - top: 0; -} - -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-moz-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-ms-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} - -@-o-keyframes progress-bar-stripes { - from { - background-position: 0 0; - } - to { - background-position: 40px 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: #f7f7f7; - background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9)); - background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9); - background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9); - background-repeat: repeat-x; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0); - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); -} - -.progress .bar { - float: left; - width: 0; - height: 100%; - font-size: 12px; - color: #ffffff; - text-align: center; - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - background-color: #0e90d2; - background-image: -moz-linear-gradient(top, #149bdf, #0480be); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be)); - background-image: -webkit-linear-gradient(top, #149bdf, #0480be); - background-image: -o-linear-gradient(top, #149bdf, #0480be); - background-image: linear-gradient(to bottom, #149bdf, #0480be); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0); - -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; - -webkit-transition: width 0.6s ease; - -moz-transition: width 0.6s ease; - -o-transition: width 0.6s ease; - transition: width 0.6s ease; -} - -.progress .bar + .bar { - -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); - -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); - box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15); -} - -.progress-striped .bar { - background-color: #149bdf; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - -webkit-background-size: 40px 40px; - -moz-background-size: 40px 40px; - -o-background-size: 40px 40px; - background-size: 40px 40px; -} - -.progress.active .bar { - -webkit-animation: progress-bar-stripes 2s linear infinite; - -moz-animation: progress-bar-stripes 2s linear infinite; - -ms-animation: progress-bar-stripes 2s linear infinite; - -o-animation: progress-bar-stripes 2s linear infinite; - animation: progress-bar-stripes 2s linear infinite; -} - -.progress-danger .bar, -.progress .bar-danger { - background-color: #dd514c; - background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35)); - background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35); - background-image: -o-linear-gradient(top, #ee5f5b, #c43c35); - background-image: linear-gradient(to bottom, #ee5f5b, #c43c35); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0); -} - -.progress-danger.progress-striped .bar, -.progress-striped .bar-danger { - background-color: #ee5f5b; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-success .bar, -.progress .bar-success { - background-color: #5eb95e; - background-image: -moz-linear-gradient(top, #62c462, #57a957); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957)); - background-image: -webkit-linear-gradient(top, #62c462, #57a957); - background-image: -o-linear-gradient(top, #62c462, #57a957); - background-image: linear-gradient(to bottom, #62c462, #57a957); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0); -} - -.progress-success.progress-striped .bar, -.progress-striped .bar-success { - background-color: #62c462; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-info .bar, -.progress .bar-info { - background-color: #4bb1cf; - background-image: -moz-linear-gradient(top, #5bc0de, #339bb9); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9)); - background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9); - background-image: -o-linear-gradient(top, #5bc0de, #339bb9); - background-image: linear-gradient(to bottom, #5bc0de, #339bb9); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0); -} - -.progress-info.progress-striped .bar, -.progress-striped .bar-info { - background-color: #5bc0de; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.progress-warning .bar, -.progress .bar-warning { - background-color: #faa732; - background-image: -moz-linear-gradient(top, #fbb450, #f89406); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406)); - background-image: -webkit-linear-gradient(top, #fbb450, #f89406); - background-image: -o-linear-gradient(top, #fbb450, #f89406); - background-image: linear-gradient(to bottom, #fbb450, #f89406); - background-repeat: repeat-x; - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0); -} - -.progress-warning.progress-striped .bar, -.progress-striped .bar-warning { - background-color: #fbb450; - background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent)); - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); -} - -.accordion { - margin-bottom: 20px; -} - -.accordion-group { - margin-bottom: 2px; - border: 1px solid #e5e5e5; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -.accordion-heading { - border-bottom: 0; -} - -.accordion-heading .accordion-toggle { - display: block; - padding: 8px 15px; -} - -.accordion-toggle { - cursor: pointer; -} - -.accordion-inner { - padding: 9px 15px; - border-top: 1px solid #e5e5e5; -} - -.carousel { - position: relative; - margin-bottom: 20px; - line-height: 1; -} - -.carousel-inner { - position: relative; - width: 100%; - overflow: hidden; -} - -.carousel-inner > .item { - position: relative; - display: none; - -webkit-transition: 0.6s ease-in-out left; - -moz-transition: 0.6s ease-in-out left; - -o-transition: 0.6s ease-in-out left; - transition: 0.6s ease-in-out left; -} - -.carousel-inner > .item > img { - display: block; - line-height: 1; -} - -.carousel-inner > .active, -.carousel-inner > .next, -.carousel-inner > .prev { - display: block; -} - -.carousel-inner > .active { - left: 0; -} - -.carousel-inner > .next, -.carousel-inner > .prev { - position: absolute; - top: 0; - width: 100%; -} - -.carousel-inner > .next { - left: 100%; -} - -.carousel-inner > .prev { - left: -100%; -} - -.carousel-inner > .next.left, -.carousel-inner > .prev.right { - left: 0; -} - -.carousel-inner > .active.left { - left: -100%; -} - -.carousel-inner > .active.right { - left: 100%; -} - -.carousel-control { - position: absolute; - top: 40%; - left: 15px; - width: 40px; - height: 40px; - margin-top: -20px; - font-size: 60px; - font-weight: 100; - line-height: 30px; - color: #ffffff; - text-align: center; - background: #222222; - border: 3px solid #ffffff; - -webkit-border-radius: 23px; - -moz-border-radius: 23px; - border-radius: 23px; - opacity: 0.5; - filter: alpha(opacity=50); -} - -.carousel-control.right { - right: 15px; - left: auto; -} - -.carousel-control:hover { - color: #ffffff; - text-decoration: none; - opacity: 0.9; - filter: alpha(opacity=90); -} - -.carousel-caption { - position: absolute; - right: 0; - bottom: 0; - left: 0; - padding: 15px; - background: #333333; - background: rgba(0, 0, 0, 0.75); -} - -.carousel-caption h4, -.carousel-caption p { - line-height: 20px; - color: #ffffff; -} - -.carousel-caption h4 { - margin: 0 0 5px; -} - -.carousel-caption p { - margin-bottom: 0; -} - -.hero-unit { - padding: 60px; - margin-bottom: 30px; - font-size: 18px; - font-weight: 200; - line-height: 30px; - color: inherit; - background-color: #eeeeee; - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; -} - -.hero-unit h1 { - margin-bottom: 0; - font-size: 60px; - line-height: 1; - letter-spacing: -1px; - color: inherit; -} - -.hero-unit li { - line-height: 30px; -} - -.pull-right { - float: right; -} - -.pull-left { - float: left; -} - -.hide { - display: none; -} - -.show { - display: block; -} - -.invisible { - visibility: hidden; -} - -.affix { - position: fixed; -} diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap.min.css b/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap.min.css deleted file mode 100644 index 140f731dfa..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/css/bootstrap.min.css +++ /dev/null @@ -1,9 +0,0 @@ -/*! - * Bootstrap v2.2.2 - * - * Copyright 2012 Twitter, Inc - * Licensed under the Apache License v2.0 - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Designed and built with all the love in the world @twitter by @mdo and @fat. - */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}label,select,button,input[type="button"],input[type="reset"],input[type="submit"],input[type="radio"],input[type="checkbox"]{cursor:pointer}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-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:hover{color:#808080}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}ul.inline,ol.inline{margin-left:0;list-style:none}ul.inline>li,ol.inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.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}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;white-space:nowrap;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,textarea:focus:invalid,select:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,textarea:focus:invalid:focus,select:focus:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child,.input-append .uneditable-input+.btn-group .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child,.input-append .btn-group:last-child>.dropdown-toggle{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .input-append+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child>th:first-child,.table-bordered tbody:first-child tr:first-child>td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child>th:last-child,.table-bordered tbody:first-child tr:first-child>td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child>th:first-child,.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child>th:last-child,.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{z-index:1051;margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini [class^="icon-"],.btn-mini [class*=" icon-"]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical>.btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-success h4{color:#468847}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown>a:hover .caret{border-top-color:#555;border-bottom-color:#555}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .brand{color:#999}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb>li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:10%;left:50%;z-index:1050;width:560px;margin-left:-280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:10%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.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}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;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-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,0.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;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,0.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,0.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,0.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}.label:empty,.badge:empty{display:none}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 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:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed} diff --git a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/js/bootstrap.js b/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/js/bootstrap.js index 6c15a58329..44109f62d4 100644 --- a/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/js/bootstrap.js +++ b/openid-connect-server-webapp/src/main/webapp/resources/bootstrap2/js/bootstrap.js @@ -1,8 +1,8 @@ /* =================================================== - * bootstrap-transition.js v2.2.2 - * http://twitter.github.com/bootstrap/javascript.html#transitions + * bootstrap-transition.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#transitions * =================================================== - * Copyright 2012 Twitter, Inc. + * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,10 +58,10 @@ }) }(window.jQuery);/* ========================================================== - * bootstrap-alert.js v2.2.2 - * http://twitter.github.com/bootstrap/javascript.html#alerts + * bootstrap-alert.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#alerts * ========================================================== - * Copyright 2012 Twitter, Inc. + * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -156,10 +156,10 @@ $(document).on('click.alert.data-api', dismiss, Alert.prototype.close) }(window.jQuery);/* ============================================================ - * bootstrap-button.js v2.2.2 - * http://twitter.github.com/bootstrap/javascript.html#buttons + * bootstrap-button.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#buttons * ============================================================ - * Copyright 2012 Twitter, Inc. + * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -260,10 +260,10 @@ }) }(window.jQuery);/* ========================================================== - * bootstrap-carousel.js v2.2.2 - * http://twitter.github.com/bootstrap/javascript.html#carousel + * bootstrap-carousel.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#carousel * ========================================================== - * Copyright 2012 Twitter, Inc. + * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -289,6 +289,7 @@ var Carousel = function (element, options) { this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') this.options = options this.options.pause == 'hover' && this.$element .on('mouseenter', $.proxy(this.pause, this)) @@ -299,19 +300,24 @@ cycle: function (e) { if (!e) this.paused = false + if (this.interval) clearInterval(this.interval); this.options.interval && !this.paused && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) return this } + , getActiveIndex: function () { + this.$active = this.$element.find('.item.active') + this.$items = this.$active.parent().children() + return this.$items.index(this.$active) + } + , to: function (pos) { - var $active = this.$element.find('.item.active') - , children = $active.parent().children() - , activePos = children.index($active) + var activeIndex = this.getActiveIndex() , that = this - if (pos > (children.length - 1) || pos < 0) return + if (pos > (this.$items.length - 1) || pos < 0) return if (this.sliding) { return this.$element.one('slid', function () { @@ -319,18 +325,18 @@ }) } - if (activePos == pos) { + if (activeIndex == pos) { return this.pause().cycle() } - return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos])) + return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) } , pause: function (e) { if (!e) this.paused = true if (this.$element.find('.next, .prev').length && $.support.transition.end) { this.$element.trigger($.support.transition.end) - this.cycle() + this.cycle(true) } clearInterval(this.interval) this.interval = null @@ -364,10 +370,19 @@ e = $.Event('slide', { relatedTarget: $next[0] + , direction: direction }) if ($next.hasClass('active')) return + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + this.$element.one('slid', function () { + var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) + $nextIndicator && $nextIndicator.addClass('active') + }) + } + if ($.support.transition && this.$element.hasClass('slide')) { this.$element.trigger(e) if (e.isDefaultPrevented()) return @@ -412,7 +427,7 @@ if (!data) $this.data('carousel', (data = new Carousel(this, options))) if (typeof option == 'number') data.to(option) else if (action) data[action]() - else if (options.interval) data.cycle() + else if (options.interval) data.pause().cycle() }) } @@ -435,19 +450,26 @@ /* CAROUSEL DATA-API * ================= */ - $(document).on('click.carousel.data-api', '[data-slide]', function (e) { + $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) { var $this = $(this), href , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7 , options = $.extend({}, $target.data(), $this.data()) + , slideIndex + $target.carousel(options) + + if (slideIndex = $this.attr('data-slide-to')) { + $target.data('carousel').pause().to(slideIndex).cycle() + } + e.preventDefault() }) }(window.jQuery);/* ============================================================= - * bootstrap-collapse.js v2.2.2 - * http://twitter.github.com/bootstrap/javascript.html#collapse + * bootstrap-collapse.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#collapse * ============================================================= - * Copyright 2012 Twitter, Inc. + * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -497,7 +519,7 @@ , actives , hasData - if (this.transitioning) return + if (this.transitioning || this.$element.hasClass('in')) return dimension = this.dimension() scroll = $.camelCase(['scroll', dimension].join('-')) @@ -517,7 +539,7 @@ , hide: function () { var dimension - if (this.transitioning) return + if (this.transitioning || !this.$element.hasClass('in')) return dimension = this.dimension() this.reset(this.$element[dimension]()) this.transition('removeClass', $.Event('hide'), 'hidden') @@ -574,7 +596,7 @@ return this.each(function () { var $this = $(this) , data = $this.data('collapse') - , options = typeof option == 'object' && option + , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option) if (!data) $this.data('collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) @@ -610,10 +632,10 @@ }) }(window.jQuery);/* ============================================================ - * bootstrap-dropdown.js v2.2.2 - * http://twitter.github.com/bootstrap/javascript.html#dropdowns + * bootstrap-dropdown.js v2.3.2 + * http://getbootstrap.com/2.3.2/javascript.html#dropdowns * ============================================================ - * Copyright 2012 Twitter, Inc. + * Copyright 2013 Twitter, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -663,6 +685,10 @@ clearMenus() if (!isActive) { + if ('ontouchstart' in document.documentElement) { + // if mobile we we use a backdrop because click events don't delegate + $('
  • ',minLength:1},$.fn.typeahead.Constructor=Typeahead,$.fn.typeahead.noConflict=function(){return $.fn.typeahead=old,this},$(document).on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(e){var $this=$(this);$this.data("typeahead")||(e.preventDefault(),$this.typeahead($this.data()))})}(window.jQuery),!function($){"use strict";var Affix=function(element,options){this.options=$.extend({},$.fn.affix.defaults,options),this.$window=$(window).on("scroll.affix.data-api",$.proxy(this.checkPosition,this)).on("click.affix.data-api",$.proxy(function(){setTimeout($.proxy(this.checkPosition,this),1)},this)),this.$element=$(element),this.checkPosition()};Affix.prototype.checkPosition=function(){if(this.$element.is(":visible")){var affix,scrollHeight=$(document).height(),scrollTop=this.$window.scrollTop(),position=this.$element.offset(),offset=this.options.offset,offsetBottom=offset.bottom,offsetTop=offset.top,reset="affix affix-top affix-bottom";"object"!=typeof offset&&(offsetBottom=offsetTop=offset),"function"==typeof offsetTop&&(offsetTop=offset.top()),"function"==typeof offsetBottom&&(offsetBottom=offset.bottom()),affix=null!=this.unpin&&scrollTop+this.unpin<=position.top?!1:null!=offsetBottom&&position.top+this.$element.height()>=scrollHeight-offsetBottom?"bottom":null!=offsetTop&&offsetTop>=scrollTop?"top":!1,this.affixed!==affix&&(this.affixed=affix,this.unpin="bottom"==affix?position.top-scrollTop:null,this.$element.removeClass(reset).addClass("affix"+(affix?"-"+affix:"")))}};var old=$.fn.affix;$.fn.affix=function(option){return this.each(function(){var $this=$(this),data=$this.data("affix"),options="object"==typeof option&&option;data||$this.data("affix",data=new Affix(this,options)),"string"==typeof option&&data[option]()})},$.fn.affix.Constructor=Affix,$.fn.affix.defaults={offset:0},$.fn.affix.noConflict=function(){return $.fn.affix=old,this},$(window).on("load",function(){$('[data-spy="affix"]').each(function(){var $spy=$(this),data=$spy.data();data.offset=data.offset||{},data.offsetBottom&&(data.offset.bottom=data.offsetBottom),data.offsetTop&&(data.offset.top=data.offsetTop),$spy.affix(data)})})}(window.jQuery); \ No newline at end of file +!function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('