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 cc7301fd52..e663c16b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -local-values.conf target *~ bin 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 a0129ef4b0..0e640e493b 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,8 +1,9 @@ -Copyright 2015 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 index deafbbd484..610579f550 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 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) +[![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. @@ -25,9 +25,7 @@ The authors and key contributors of the project include: * [Steve Moore](https://github.com/srmoore) * [Mike Derryberry](https://github.com/mtderryberry) * [William Kim](https://github.com/wikkim) +* [Mark Janssen](https://github.com/praseodym) - - -Copyright ©2015, [The MITRE Corporation](http://www.mitre.org/) - and the [MIT Kerberos and Internet Trust Consortium](http://kit.mit.edu/). Licensed under the Apache 2.0 license, for details see `LICENSE.txt`. +Licensed under the Apache 2.0 license, for details see `LICENSE.txt`. 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 69f93d97ca..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 100644 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 ba97fe29c1..5bddcdb6ed 100644 --- a/openid-connect-client/README.md +++ b/openid-connect-client/README.md @@ -2,7 +2,7 @@ ## 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. +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 ## diff --git a/openid-connect-client/pom.xml b/openid-connect-client/pom.xml index 4f74229edc..3fbbd9e5ec 100644 --- a/openid-connect-client/pom.xml +++ b/openid-connect-client/pom.xml @@ -1,27 +1,28 @@ + 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.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT .. openid-connect-client 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 0935fe529a..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,8 @@ *******************************************************************************/ 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; @@ -54,8 +57,6 @@ import com.google.gson.JsonParser; import com.nimbusds.jose.util.Base64; -import static org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod.SECRET_BASIC; - /** * This ResourceServerTokenServices implementation introspects incoming tokens at a * server's introspection endpoint URL and passes an Authentication object along @@ -68,30 +69,35 @@ public class IntrospectingTokenService implements ResourceServerTokenServices { private IntrospectionConfigurationService introspectionConfigurationService; private IntrospectionAuthorityGranter introspectionAuthorityGranter = new SimpleIntrospectionAuthorityGranter(); - private int defaultExpireTime = 300000; // 5 minutes in milliseconds + 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 HttpClient httpClient = HttpClientBuilder.create() - .useSystemProperties() - .build(); - private HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + + 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)))) { + 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(); @@ -150,7 +156,7 @@ public int getDefaultExpireTime() { public void setDefaultExpireTime(int defaultExpireTime) { this.defaultExpireTime = defaultExpireTime; } - + /** * check if forcing a cache expire time maximum value * @return the forceCacheExpireTime setting @@ -198,10 +204,10 @@ public boolean isCacheTokens() { 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 + * 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. @@ -211,7 +217,7 @@ public void setCacheTokens(boolean cacheTokens) { private TokenCacheObject checkCache(String key) { if (cacheTokens && authCache.containsKey(key)) { TokenCacheObject tco = authCache.get(key); - + if (tco != null && tco.cacheExpire != null && tco.cacheExpire.after(new Date())) { return tco; } else { @@ -235,8 +241,16 @@ private OAuth2Request createStoredRequest(final JsonObject token) { 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) { @@ -246,7 +260,7 @@ private OAuth2AccessToken createAccessToken(final JsonObject token, final String /** * Validate a token string against the introspection endpoint, - * then parse it and store it in the local cache if caching is enabled. + * 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 @@ -298,6 +312,7 @@ 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 @@ -320,7 +335,7 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE 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); 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 3e78e4b9c9..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 d9559cefeb..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 e844cc7051..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 613364c51f..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 * */ 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 6ddb0a52b0..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 86dd38f8c2..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 3b8a2564ff..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client; @@ -31,21 +32,21 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; +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 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"); @@ -56,18 +57,18 @@ public Collection mapAuthorities(JWT idToken, UserIn Set out = new HashSet<>(); try { - ReadOnlyJWTClaimsSet claims = idToken.getJWTClaimsSet(); - + JWTClaimsSet claims = idToken.getJWTClaimsSet(); + SubjectIssuerGrantedAuthority authority = new SubjectIssuerGrantedAuthority(claims.getSubject(), claims.getIssuer()); out.add(authority); if (admins.contains(authority)) { out.add(ROLE_ADMIN); } - + // everybody's a user by default out.add(ROLE_USER); - + } catch (ParseException e) { logger.error("Unable to parse ID Token inside of authorities mapper (huh?)"); } 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 fbbe0a7b59..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,13 +17,21 @@ *******************************************************************************/ 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.ServletException; import javax.servlet.http.HttpServletRequest; @@ -35,6 +44,7 @@ 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; @@ -70,33 +80,30 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.util.Base64; +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.ReadOnlyJWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -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; - /** * 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; @@ -110,9 +117,11 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi private SymmetricKeyJWTValidatorCacheService symmetricCacheService; // signer based on keypair for this client (for outgoing auth requests) - @Autowired + @Autowired(required=false) private JWTSigningAndValidationService authenticationSignerService; + @Autowired(required=false) + private HttpClient httpClient; /* * Modular services to build out client filter. @@ -161,9 +170,9 @@ public void afterPropertiesSet() { /* * This is the main entry point for the filter. - * + * * (non-Javadoc) - * + * * @see org.springframework.security.web.authentication. * AbstractAuthenticationProcessingFilter * #attemptAuthentication(javax.servlet.http.HttpServletRequest, @@ -196,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 @@ -263,6 +272,26 @@ protected void handleAuthorizationRequest(HttpServletRequest request, HttpServle Map options = authOptions.getOptions(serverConfig, clientConfig, request); + // 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); @@ -286,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 @@ -305,6 +332,11 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ 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) { form.add("redirect_uri", redirectUri); @@ -312,14 +344,14 @@ protected Authentication handleAuthorizationCodeResponse(HttpServletRequest requ // Handle Token Endpoint interaction - HttpClient httpClient = HttpClientBuilder.create() - .useSystemProperties() - .setDefaultRequestConfig( - RequestConfig.custom() - .setSocketTimeout(httpSocketTimeout) - .build() - ) - .build(); + if(httpClient == null) { + httpClient = HttpClientBuilder.create() + .useSystemProperties() + .setDefaultRequestConfig(RequestConfig.custom() + .setSocketTimeout(httpSocketTimeout) + .build()) + .build(); + } HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); @@ -333,8 +365,8 @@ 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", - UriUtils.encodePathSegment(clientConfig.getClientId(), "UTF-8"), + String.format("Basic %s", Base64.encode(String.format("%s:%s", + UriUtils.encodePathSegment(clientConfig.getClientId(), "UTF-8"), UriUtils.encodePathSegment(clientConfig.getClientSecret(), "UTF-8"))))); return httpRequest; @@ -352,9 +384,9 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE JWSAlgorithm alg = clientConfig.getTokenEndpointAuthSigningAlg(); if (SECRET_JWT.equals(clientConfig.getTokenEndpointAuthMethod()) && - (alg.equals(JWSAlgorithm.HS256) - || alg.equals(JWSAlgorithm.HS384) - || alg.equals(JWSAlgorithm.HS512))) { + (JWSAlgorithm.HS256.equals(alg) + || JWSAlgorithm.HS384.equals(alg) + || JWSAlgorithm.HS512.equals(alg))) { // generate one based on client secret signer = symmetricCacheService.getSymmetricValidtor(clientConfig.getClient()); @@ -373,21 +405,25 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE throw new AuthenticationServiceException("Couldn't find required signer service for use with private key auth."); } - JWTClaimsSet claimsSet = new JWTClaimsSet(); + JWTClaimsSet.Builder claimsSet = new JWTClaimsSet.Builder(); - claimsSet.setIssuer(clientConfig.getClientId()); - claimsSet.setSubject(clientConfig.getClientId()); - claimsSet.setAudience(Lists.newArrayList(serverConfig.getTokenEndpointUri())); + 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.setExpirationTime(exp); + claimsSet.expirationTime(exp); Date now = new Date(System.currentTimeMillis()); - claimsSet.setIssueTime(now); - claimsSet.setNotBeforeTime(now); + claimsSet.issueTime(now); + claimsSet.notBeforeTime(now); - SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claimsSet); + 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); @@ -467,7 +503,7 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE 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 = null; @@ -581,10 +617,10 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE // construct an PendingOIDCAuthenticationToken and return a Authentication object w/the userId and the idToken - PendingOIDCAuthenticationToken token = new PendingOIDCAuthenticationToken(idClaims.getSubject(), idClaims.getIssuer(), - serverConfig, + PendingOIDCAuthenticationToken token = new PendingOIDCAuthenticationToken(idClaims.getSubject(), idClaims.getIssuer(), + serverConfig, idToken, accessTokenValue, refreshTokenValue); - + Authentication authentication = this.getAuthenticationManager().authenticate(token); return authentication; @@ -599,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 @@ -614,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); } /** @@ -674,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) { @@ -706,7 +762,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, if (!Strings.isNullOrEmpty(target)) { session.removeAttribute(TARGET_SESSION_VARIABLE); - target = deepLinkFilter.filter(target); + if (deepLinkFilter != null) { + target = deepLinkFilter.filter(target); + } response.sendRedirect(target); } else { 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 156ff4dd73..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -34,19 +35,19 @@ /** * @author nemonik, Justin Richer - * + * */ public class OIDCAuthenticationProvider implements AuthenticationProvider { private static Logger logger = LoggerFactory.getLogger(OIDCAuthenticationProvider.class); - + private UserInfoFetcher userInfoFetcher = new UserInfoFetcher(); private OIDCAuthoritiesMapper authoritiesMapper = new NamedAdminAuthoritiesMapper(); /* * (non-Javadoc) - * + * * @see org.springframework.security.authentication.AuthenticationProvider# * authenticate(org.springframework.security.core.Authentication) */ @@ -60,7 +61,7 @@ public Authentication authenticate(final Authentication authentication) throws A if (authentication instanceof PendingOIDCAuthenticationToken) { PendingOIDCAuthenticationToken token = (PendingOIDCAuthenticationToken) authentication; - + // get the ID Token value out JWT idToken = token.getIdToken(); @@ -68,7 +69,7 @@ public Authentication authenticate(final Authentication authentication) throws A UserInfo userInfo = userInfoFetcher.loadUserInfo(token); if (userInfo == null) { - // user info not found -- could be an error, could be fine + // 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())) { @@ -86,7 +87,7 @@ public Authentication authenticate(final Authentication authentication) throws A /** * 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 @@ -99,6 +100,13 @@ protected Authentication createAuthenticationToken(PendingOIDCAuthenticationToke token.getIdToken(), token.getAccessTokenValue(), token.getRefreshTokenValue()); } + /** + * @param userInfoFetcher + */ + public void setUserInfoFetcher(UserInfoFetcher userInfoFetcher) { + this.userInfoFetcher = userInfoFetcher; + } + /** * @param authoritiesMapper */ @@ -108,7 +116,7 @@ public void setAuthoritiesMapper(OIDCAuthoritiesMapper authoritiesMapper) { /* * (non-Javadoc) - * + * * @see * org.springframework.security.authentication.AuthenticationProvider#supports * (java.lang.Class) 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 index 4300d26a96..0ee1d1c66e 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -32,7 +31,7 @@ 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) + * @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 index 466fa82ce7..b7725bbe07 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -19,7 +18,7 @@ /** * Simple target URI checker, checks whether the string in question starts * with a configured prefix. Returns "/" if the match fails. - * + * * @author jricher * */ 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 d9686fe3be..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,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +14,7 @@ * 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 index 69baca9fdc..1fca0bfeb1 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -20,7 +19,7 @@ public interface TargetLinkURIChecker { /** * Check the parameter to make sure that it's a valid deep-link into this application. - * + * * @param target * @return */ 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 3cb0fe1975..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,6 +19,9 @@ 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.client.utils.URIBuilder; @@ -37,11 +41,15 @@ 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 * */ @@ -52,28 +60,51 @@ public class UserInfoFetcher { */ private static final Logger logger = LoggerFactory.getLogger(UserInfoFetcher.class); - public UserInfo loadUserInfo(final PendingOIDCAuthenticationToken token) { + private LoadingCache cache; + + 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); } - try { + @Override + public UserInfo load(final PendingOIDCAuthenticationToken token) throws URISyntaxException { + + ServerConfiguration serverConfiguration = token.getServerConfiguration(); - // if we got this far, try to actually get the userinfo - HttpClient httpClient = HttpClientBuilder.create() - .useSystemProperties() - .build(); + if (serverConfiguration == null) { + logger.warn("No server configuration found."); + return null; + } - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + if (Strings.isNullOrEmpty(serverConfiguration.getUserInfoUri())) { + logger.warn("No userinfo endpoint, not fetching."); + return null; + } String userInfoString = null; @@ -109,18 +140,18 @@ protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOE JsonObject userInfoJson = new JsonParser().parse(userInfoString).getAsJsonObject(); - UserInfo userInfo = DefaultUserInfo.fromJson(userInfoJson); + UserInfo userInfo = fromJson(userInfoJson); return userInfo; } else { - // didn't get anything, return null - return null; + // didn't get anything throw exception + throw new IllegalArgumentException("Unable to load user info"); } - } catch (Exception e) { - logger.warn("Error fetching userinfo", e); - return null; - } + } } + 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 d7b9246b4d..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 3cdab0ed46..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 6d849c2e99..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 df97f1cfaf..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,14 +16,14 @@ * 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 b0bd91ec66..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; @@ -27,11 +28,11 @@ import org.mitre.openid.connect.config.ServerConfiguration; /** - * + * * 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 * */ @@ -39,17 +40,17 @@ 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 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 723ec1ee18..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service; @@ -25,8 +26,8 @@ import org.mitre.openid.connect.config.ServerConfiguration; /** - * Builds a URL string to the IdP's authorization endpoint. - * + * Builds a URL string to the IdP's authorization endpoint. + * * @author jricher * */ @@ -38,7 +39,7 @@ public interface AuthRequestUrlBuilder { * @param redirectUri * @param nonce * @param state - * @param loginHint + * @param loginHint * @return */ 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 49bfed5042..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 858f7175a7..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 048176ce5e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 cef75fb698..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 5ea5a0522f..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -39,6 +40,8 @@ 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; @@ -70,7 +73,11 @@ public class DynamicRegistrationClientConfigurationService implements ClientConf 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 @@ -158,21 +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 = HttpClientBuilder.create() - .useSystemProperties() - .build(); - - 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); @@ -191,15 +202,18 @@ public RegisteredClient load(ServerConfiguration serverConfig) throws Exception 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 { if (knownClient.getClientId() == null) { @@ -211,12 +225,16 @@ public RegisteredClient load(ServerConfiguration serverConfig) throws Exception 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 d7ce15aa69..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,17 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; +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; @@ -41,17 +49,10 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; -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; - /** - * + * * Dynamically fetches OpenID Connect server configurations based on the issuer. Caches the server configurations. - * + * * @author jricher * */ @@ -69,8 +70,12 @@ public class DynamicServerConfigurationService implements ServerConfigurationSer 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)); } /** @@ -126,12 +131,13 @@ public ServerConfiguration getServerConfiguration(String issuer) { * */ private class OpenIDConnectServiceConfigurationFetcher extends CacheLoader { - private HttpClient httpClient = HttpClientBuilder.create() - .useSystemProperties() - .build(); - 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); 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 1664cb35ad..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -58,33 +59,33 @@ public class EncryptedAuthRequestUrlBuilder implements AuthRequestUrlBuilder { 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.setClaim("login_hint", 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()); 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 94b4b8e7d9..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 1e96da0acd..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -26,11 +27,11 @@ 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 * */ 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 cdd18778c6..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 8963016ef1..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; 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 2a511ed20a..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -55,20 +56,20 @@ public class JsonFileRegisteredClientService implements RegisteredClientService 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) { - return ClientDetailsEntityJsonProcessor.serialize(src); - } - }) - .registerTypeAdapter(RegisteredClient.class, new JsonDeserializer() { - @Override - public RegisteredClient deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - return ClientDetailsEntityJsonProcessor.parseRegistered(json); - } - }) - .setPrettyPrinting() - .create(); + .registerTypeAdapter(RegisteredClient.class, new JsonSerializer() { + @Override + public JsonElement serialize(RegisteredClient src, Type typeOfSrc, JsonSerializationContext context) { + return ClientDetailsEntityJsonProcessor.serialize(src); + } + }) + .registerTypeAdapter(RegisteredClient.class, new JsonDeserializer() { + @Override + public RegisteredClient deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return ClientDetailsEntityJsonProcessor.parseRegistered(json); + } + }) + .setPrettyPrinting() + .create(); private File file; 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 d9a269b03e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -33,9 +34,9 @@ import com.google.common.base.Strings; /** - * + * * Builds an auth request redirect URI with normal query parameters. - * + * * @author jricher * */ @@ -63,7 +64,7 @@ public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredCl for (Entry option : options.entrySet()) { uriBuilder.addParameter(option.getKey(), option.getValue()); } - + // if there's a login hint, send it if (!Strings.isNullOrEmpty(loginHint)) { uriBuilder.addParameter("login_hint", loginHint); 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 88bb1d234c..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -52,30 +53,30 @@ public class SignedAuthRequestUrlBuilder implements AuthRequestUrlBuilder { 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.setClaim("login_hint", loginHint); + claims.claim("login_hint", loginHint); } JWSAlgorithm alg = clientConfig.getRequestObjectSigningAlg(); @@ -83,7 +84,7 @@ public String buildAuthRequestUrl(ServerConfiguration serverConfig, RegisteredCl alg = signingAndValidationService.getDefaultSigningAlgorithm(); } - SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claims); + SignedJWT jwt = new SignedJWT(new JWSHeader(alg), claims.build()); signingAndValidationService.signJwt(jwt, alg); 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 134c67edce..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -29,9 +30,9 @@ import org.mitre.openid.connect.config.ServerConfiguration; /** - * + * * Always returns the same set of options. - * + * * @author jricher * */ 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 69edf2b7b2..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 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 4bb519e706..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 * */ 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 aefcae9478..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 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 f1c89793f9..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 * */ 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 31a4ed36c6..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.client.service.impl; @@ -63,7 +64,17 @@ public class WebfingerIssuerService implements IssuerService { 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<>(); @@ -77,14 +88,18 @@ public class WebfingerIssuerService implements IssuerService { * URL of the page to forward to if no identifier is given. */ 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) @@ -96,16 +111,16 @@ 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, identifier, null); + 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; @@ -192,30 +207,34 @@ public void setForceHttps(boolean forceHttps) { * @author jricher * */ - private class WebfingerIssuerFetcher extends CacheLoader { - private HttpClient httpClient = HttpClientBuilder.create() - .useSystemProperties() - .build(); - 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(); - + + // preserving http scheme is strictly for demo system use only. if (!Strings.isNullOrEmpty(scheme) &&scheme.equals("http")) { if (forceHttps) { - throw new IllegalArgumentException("Scheme must start with htps"); + 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://"; } @@ -227,17 +246,17 @@ 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"); try { - + // 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(); @@ -247,9 +266,18 @@ public String load(UriComponents key) throws Exception { 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 - return linkObj.get("href").getAsString(); + 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); + } } } } @@ -262,11 +290,11 @@ public String load(UriComponents key) throws Exception { if (key.getScheme().equals("http") || key.getScheme().equals("https")) { // 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: " + key.toString()); - return key.toString(); + 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()); + 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 index 70ee62cd91..051b5a26c2 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. 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 53fe382e0c..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 fc0a552926..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 af4ea92096..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 b9b5767820..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -43,7 +44,7 @@ import com.nimbusds.jose.jwk.KeyUse; import com.nimbusds.jose.jwk.RSAKey; 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; @@ -52,7 +53,7 @@ /** * @author wkim - * + * */ public class TestSignedAuthRequestUrlBuilder { @@ -91,7 +92,7 @@ public class TestSignedAuthRequestUrlBuilder { @Before public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException { - RSAKey key = new RSAKey(new Base64URL(n), new Base64URL(e), new Base64URL(d), KeyUse.SIGNATURE, null, 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); @@ -130,7 +131,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); @@ -169,7 +170,7 @@ public void buildAuthRequestUrl_withLoginHint() { UriComponents components = builder.build(); String jwtString = components.getQueryParams().get("request").get(0); - ReadOnlyJWTClaimsSet claims = null; + JWTClaimsSet claims = null; try { SignedJWT jwt = SignedJWT.parse(jwtString); 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 c6c4c323d8..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 e499119956..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 b315f2075e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. diff --git a/openid-connect-client/src/test/resources/test-context.xml b/openid-connect-client/src/test/resources/test-context.xml index 8ed89f7282..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.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT .. openid-connect-common @@ -31,6 +32,12 @@ org.springframework spring-core + + + commons-logging + commons-logging + + org.springframework @@ -76,6 +83,30 @@ 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 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/WebfingerURLNormalizer.java b/openid-connect-common/src/main/java/org/mitre/discovery/util/WebfingerURLNormalizer.java index 08bed78d02..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -29,7 +30,7 @@ /** * Provides utility methods for normalizing and parsing URIs for use with Webfinger Discovery. - * + * * @author wkim * */ 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 bbe3b73194..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 index a01c5b309a..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 index d498d99c28..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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; @@ -75,7 +80,7 @@ public class DefaultJWTEncryptionAndDecryptionService implements JWTEncryptionAn /** * 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 @@ -89,7 +94,7 @@ 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 @@ -223,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); @@ -272,11 +294,11 @@ public Collection getAllEncryptionAlgsSupported() { 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; 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 index 5d24fe483e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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,7 +68,7 @@ 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 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 index 02381f24c5..3e46b9b4bd 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -39,10 +38,10 @@ 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 * */ @@ -50,16 +49,16 @@ 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; @@ -74,7 +73,7 @@ public ClientKeyCacheService() { .build(new JWKSetEncryptorBuilder()); } - + public JWTSigningAndValidationService getValidator(ClientDetailsEntity client, JWSAlgorithm alg) { try { @@ -87,7 +86,7 @@ public JWTSigningAndValidationService getValidator(ClientDetailsEntity client, J || alg.equals(JWSAlgorithm.PS256) || alg.equals(JWSAlgorithm.PS384) || alg.equals(JWSAlgorithm.PS512)) { - + // asymmetric key if (client.getJwks() != null) { return jwksValidators.get(client.getJwks()); @@ -96,28 +95,28 @@ public JWTSigningAndValidationService getValidator(ClientDetailsEntity client, J } 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) { + } 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()); @@ -130,17 +129,17 @@ public JWTEncryptionAndDecryptionService getEncrypter(ClientDetailsEntity client 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 { @@ -152,5 +151,5 @@ public JWTSigningAndValidationService load(JWKSet key) throws Exception { } - + } 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 index ea34efd345..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -70,10 +71,10 @@ public class DefaultJWTSigningAndValidationService implements JWTSigningAndValid /** * 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 @@ -87,10 +88,10 @@ 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 @@ -160,41 +161,45 @@ 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 - if (jwk.isPrivate()) { - ECDSASigner signer = new ECDSASigner(((ECKey) jwk).getD().decodeToBigInteger()); - signers.put(id, signer); - } - - ECDSAVerifier verifier = new ECDSAVerifier(((ECKey) jwk).getX().decodeToBigInteger(), ((ECKey) jwk).getY().decodeToBigInteger()); - verifiers.put(id, verifier); - - } 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).toByteArray()); - signers.put(id, signer); - } + if (jwk.isPrivate()) { + ECDSASigner signer = new ECDSASigner((ECKey) jwk); + signers.put(id, signer); + } + + ECDSAVerifier verifier = new ECDSAVerifier((ECKey) jwk); + verifiers.put(id, verifier); - MACVerifier verifier = new MACVerifier(((OctetSequenceKey) jwk).toByteArray()); - verifiers.put(id, verifier); + } else if (jwk instanceof OctetSequenceKey) { + // build HMAC signers & verifiers - } else { - logger.warn("Unknown key type: " + jwk); + 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); + verifiers.put(id, verifier); + + } else { + logger.warn("Unknown key type: " + jwk); + } + } catch (JOSEException e) { + logger.warn("Exception loading signer/verifier", e); } } @@ -230,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; } @@ -292,11 +297,11 @@ public Collection getAllSigningAlgsSupported() { 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 1b1216390c..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.jwt.signer.service.impl; @@ -32,21 +33,23 @@ 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 { @@ -66,11 +69,11 @@ 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())); } /** @@ -102,9 +105,13 @@ public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) { * */ private class JWKSetVerifierFetcher extends CacheLoader { - private HttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - private RestTemplate restTemplate = new RestTemplate(httpFactory); + 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. @@ -128,22 +135,31 @@ public JWTSigningAndValidationService load(String key) throws Exception { * */ private class JWKSetEncryptorFetcher extends CacheLoader { - private HttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - private RestTemplate restTemplate = new RestTemplate(httpFactory); + 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); + 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 index 02793ae64f..817cffefce 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -41,7 +40,7 @@ /** * Creates and caches symmetrical validators for clients based on client secrets. - * + * * @author jricher * */ @@ -66,7 +65,7 @@ public SymmetricKeyJWTValidatorCacheService() { /** * Create a symmetric signing and validation service for the given client - * + * * @param client * @return */ @@ -100,8 +99,10 @@ public JWTSigningAndValidationService load(String key) throws Exception { try { String id = "SYMMETRIC-KEY"; - - JWK jwk = new OctetSequenceKey(Base64URL.encode(key), KeyUse.SIGNATURE, null, null, id, null, null, null); + 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); 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 7ad1b94785..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -65,25 +66,25 @@ public class AuthenticationHolderEntity { private Long id; private SavedUserAuthentication userAuth; - - private Collection authorities; - + + private Collection authorities; + private Set resourceIds; - + private boolean approved; - + private String redirectUri; - + private Set responseTypes; - + private Map extensions; - + private String clientId; - + private Set scope; - + private Map requestParameters; - + public AuthenticationHolderEntity() { } @@ -116,16 +117,16 @@ public void setAuthentication(OAuth2Authentication authentication) { // pull apart the request and save its bits OAuth2Request o2Request = authentication.getOAuth2Request(); - setAuthorities(o2Request.getAuthorities()); + setAuthorities(o2Request.getAuthorities() == null ? null : new HashSet<>(o2Request.getAuthorities())); setClientId(o2Request.getClientId()); - setExtensions(o2Request.getExtensions()); + setExtensions(o2Request.getExtensions() == null ? null : new HashMap<>(o2Request.getExtensions())); setRedirectUri(o2Request.getRedirectUri()); - setRequestParameters(o2Request.getRequestParameters()); - setResourceIds(o2Request.getResourceIds()); - setResponseTypes(o2Request.getResponseTypes()); - setScope(o2Request.getScope()); + 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 { @@ -159,19 +160,15 @@ public void setUserAuth(SavedUserAuthentication userAuth) { ) @Convert(converter = SimpleGrantedAuthorityStringConverter.class) @Column(name="authority") - public Collection getAuthorities() { + public Collection getAuthorities() { return authorities; } /** * @param authorities the authorities to set */ - public void setAuthorities(Collection authorities) { - if (authorities != null) { - this.authorities = new HashSet<>(authorities); - } else { - this.authorities = null; - } + public void setAuthorities(Collection authorities) { + this.authorities = authorities; } /** @@ -191,11 +188,7 @@ public Set getResourceIds() { * @param resourceIds the resourceIds to set */ public void setResourceIds(Set resourceIds) { - if (resourceIds != null) { - this.resourceIds = new HashSet<>(resourceIds); - } else { - this.resourceIds = null; - } + this.resourceIds = resourceIds; } /** @@ -247,11 +240,7 @@ public Set getResponseTypes() { * @param responseTypes the responseTypes to set */ public void setResponseTypes(Set responseTypes) { - if (responseTypes != null) { - this.responseTypes = new HashSet<>(responseTypes); - } else { - this.responseTypes = null; - } + this.responseTypes = responseTypes; } /** @@ -273,11 +262,7 @@ public Map getExtensions() { * @param extensions the extensions to set */ public void setExtensions(Map extensions) { - if (extensions != null) { - this.extensions = new HashMap<>(extensions); - } else { - this.extensions = null; - } + this.extensions = extensions; } /** @@ -313,11 +298,7 @@ public Set getScope() { * @param scope the scope to set */ public void setScope(Set scope) { - if (scope != null) { - this.scope = new HashSet<>(scope); - } else { - this.scope = null; - } + this.scope = scope; } /** @@ -338,11 +319,7 @@ public Map getRequestParameters() { * @param requestParameters the requestParameters to set */ public void setRequestParameters(Map requestParameters) { - if (requestParameters != null) { - this.requestParameters = new HashMap<>(requestParameters); - } else { - this.requestParameters = null; - } + 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 2e636369ed..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -33,7 +34,7 @@ /** * Entity class for authorization codes - * + * * @author aanganes * */ @@ -47,7 +48,7 @@ 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; @@ -55,7 +56,7 @@ public class AuthorizationCodeEntity { private String code; private AuthenticationHolderEntity authenticationHolder; - + private Date expiration; /** @@ -67,7 +68,7 @@ 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 */ 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 e5634086e7..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -51,6 +52,8 @@ 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; @@ -59,10 +62,11 @@ 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") @@ -76,7 +80,7 @@ public class ClientDetailsEntity implements ClientDetails { 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; @@ -99,6 +103,8 @@ public class ClientDetailsEntity implements ClientDetails { private String policyUri; 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 @@ -141,6 +147,16 @@ public class ClientDetailsEntity implements ClientDetails { 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"), @@ -251,7 +267,7 @@ public Long getId() { } /** - * + * * @param id the id to set */ public void setId(Long id) { @@ -298,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 @@ -351,7 +367,7 @@ public void setAllowIntrospection(boolean allowIntrospection) { } /** - * + * */ @Override @Transient @@ -564,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 @@ -964,5 +980,107 @@ public boolean isClearAccessTokensOnRefresh() { 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 4bad4c0f9c..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -41,12 +42,12 @@ 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.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; @@ -68,9 +69,10 @@ @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_ID_TOKEN, query = "select a from OAuth2AccessTokenEntity a where a.idToken = :" + OAuth2AccessTokenEntity.PARAM_ID_TOKEN), @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_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_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) }) @org.codehaus.jackson.map.annotate.JsonSerialize(using = OAuth2AccessTokenJackson1Serializer.class) @org.codehaus.jackson.map.annotate.JsonDeserialize(using = OAuth2AccessTokenJackson1Deserializer.class) @@ -78,22 +80,24 @@ @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = OAuth2AccessTokenJackson2Deserializer.class) public class OAuth2AccessTokenEntity implements OAuth2AccessToken { + 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_ID_TOKEN = "OAuth2AccessTokenEntity.getByIdToken"; 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_ID_TOKEN = "idToken"; 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 String ID_TOKEN_FIELD_NAME = "id_token"; + 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; @@ -103,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; @@ -112,9 +114,13 @@ public class OAuth2AccessTokenEntity implements OAuth2AccessToken { private OAuth2RefreshTokenEntity refreshToken; 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 */ @@ -140,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; } /** @@ -256,34 +259,6 @@ 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 */ @@ -325,7 +300,7 @@ public int getExpiresIn() { name = "access_token_permissions", joinColumns = @JoinColumn(name = "access_token_id"), inverseJoinColumns = @JoinColumn(name = "permission_id") - ) + ) public Set getPermissions() { return permissions; } @@ -337,4 +312,24 @@ 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 ebf88e1f09..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -52,7 +53,8 @@ @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_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 { @@ -60,11 +62,13 @@ public class OAuth2RefreshTokenEntity implements OAuth2RefreshToken { 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; @@ -78,7 +82,7 @@ public class OAuth2RefreshTokenEntity implements OAuth2RefreshToken { private Date expiration; /** - * + * */ public OAuth2RefreshTokenEntity() { @@ -104,7 +108,7 @@ public void setId(Long id) { /** * The authentication in place when the original access token was * created - * + * * @return the authentication */ @ManyToOne 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 994dfbb67b..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; @@ -33,6 +34,7 @@ import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; /** * @author jricher @@ -49,7 +51,7 @@ public class RegisteredClient { private JsonObject src; /** - * + * */ public RegisteredClient() { this.client = new ClientDetailsEntity(); @@ -591,7 +593,7 @@ public Set getRequestUris() { public void setRequestUris(Set requestUris) { client.setRequestUris(requestUris); } - + /** * @return * @see org.mitre.oauth2.model.ClientDetailsEntity#getRequestObjectSigningAlg() @@ -783,6 +785,54 @@ 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 */ @@ -797,6 +847,54 @@ 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 index cf803d5b92..79231b5238 100644 --- 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 @@ -1,6 +1,25 @@ +/******************************************************************************* + * 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"; @@ -38,5 +57,5 @@ public interface RegisteredClientFields { 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 index 11594030e6..21fa34a830 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -40,7 +39,7 @@ /** * This class stands in for an original Authentication object. - * + * * @author jricher * */ @@ -49,23 +48,23 @@ public class SavedUserAuthentication implements Authentication { private static final long serialVersionUID = -1804249963940323488L; - + private Long id; - + private String name; - - private Collection authorities; - + + 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(src.getAuthorities()); + setAuthorities(new HashSet<>(src.getAuthorities())); setAuthenticated(src.isAuthenticated()); if (src instanceof SavedUserAuthentication) { @@ -80,7 +79,7 @@ public SavedUserAuthentication(Authentication src) { * Create an empty saved auth */ public SavedUserAuthentication() { - + } /** @@ -104,7 +103,7 @@ public void setId(Long id) { @Basic @Column(name="name") public String getName() { - return name; + return name; } @Override @@ -115,8 +114,8 @@ public String getName() { ) @Convert(converter = SimpleGrantedAuthorityStringConverter.class) @Column(name="authority") - public Collection getAuthorities() { - return authorities; + public Collection getAuthorities() { + return authorities; } @Override @@ -175,13 +174,9 @@ public void setName(String name) { /** * @param authorities the authorities to set */ - public void setAuthorities(Collection authorities) { - if (authorities != null) { - this.authorities = new HashSet<>(authorities); - } else { - this.authorities = null; - } + 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 02dd134d17..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 @@ -44,18 +44,15 @@ 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 defaultScope = false; // is this a default scope for newly-registered clients? private boolean restricted = false; // is this scope restricted to admin-only registration access? - private boolean structured = false; // is this a default scope for newly-registered clients? - private String structuredParamDescription; - private String structuredValue; /** * Make a blank system scope with no value @@ -162,52 +159,6 @@ public void setRestricted(boolean restricted) { this.restricted = restricted; } - /** - * @return the isStructured status - */ - @Basic - @Column(name = "structured") - public boolean isStructured() { - return structured; - } - - /** - * @param structured the structured to set - */ - public void setStructured(boolean structured) { - this.structured = structured; - } - - @Basic - @Column(name = "structured_param_description") - public String getStructuredParamDescription() { - return structuredParamDescription; - } - - /** - * @param isStructured the isStructured to set - */ - public void setStructuredParamDescription(String d) { - this.structuredParamDescription = d; - } - - - /** - * @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() */ @@ -221,13 +172,6 @@ public int hashCode() { result = prime * result + ((icon == null) ? 0 : icon.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + (restricted ? 1231 : 1237); - 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 + ((value == null) ? 0 : value.hashCode()); return result; } @@ -274,24 +218,6 @@ public boolean equals(Object obj) { if (restricted != other.restricted) { 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)) { - return false; - } if (value == null) { if (other.value != null) { return false; @@ -309,10 +235,7 @@ public boolean equals(Object obj) { public String toString() { return "SystemScope [id=" + id + ", value=" + value + ", description=" + description + ", icon=" + icon + ", defaultScope=" - + defaultScope + ", restricted=" + restricted + ", structured=" - + structured + ", structuredParamDescription=" - + structuredParamDescription + ", structuredValue=" - + structuredValue + "]"; + + 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 index 1711c839bd..1341cb4bc8 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. 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 index 80eafecdf1..a9f0355b8b 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. 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 index 94cb7f9ce3..f499e1af4b 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -61,7 +60,7 @@ public JWKSet convertToEntityAttribute(String dbData) { } 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 index 9d40edee62..c671c50fa0 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. 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 index 7f3922520e..6f69c6a88b 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -36,7 +35,7 @@ public class JWTStringConverter implements AttributeConverter { public static Logger logger = LoggerFactory.getLogger(JWTStringConverter.class); - + @Override public String convertToDatabaseColumn(JWT attribute) { if (attribute != 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 index a2918bddce..3ee6305372 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -22,7 +21,6 @@ import com.google.common.base.Strings; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import com.google.gson.JsonParser; /** @@ -33,7 +31,7 @@ public class JsonElementStringConverter implements AttributeConverter { private JsonParser parser = new JsonParser(); - + @Override public String convertToDatabaseColumn(JsonElement attribute) { if (attribute != 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 index d62e54fb41..0c3e523884 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -27,12 +26,12 @@ import org.slf4j.LoggerFactory; /** - * Translates a Serializable object of certain primitive types + * 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 * */ @@ -40,7 +39,7 @@ public class SerializableStringConverter implements AttributeConverter { private static Logger logger = LoggerFactory.getLogger(SerializableStringConverter.class); - + @Override public String convertToDatabaseColumn(Serializable attribute) { if (attribute == null) { 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 index dec3e4b86f..875387508f 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -35,7 +34,7 @@ public String convertToDatabaseColumn(SimpleGrantedAuthority attribute) { return attribute.getAuthority(); } else { return null; - } + } } @Override 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 6c11942cbb..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,6 +19,7 @@ import java.util.List; +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.AuthenticationHolderEntity; public interface AuthenticationHolderRepository { @@ -31,5 +33,5 @@ public interface AuthenticationHolderRepository { 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 dbdaa4e04e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,11 +19,12 @@ import java.util.Collection; +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.AuthorizationCodeEntity; /** * Interface for saving and consuming OAuth2 authorization codes as AuthorizationCodeEntitys. - * + * * @author aanganes * */ @@ -30,7 +32,7 @@ public interface AuthorizationCodeRepository { /** * Save an AuthorizationCodeEntity to the repository - * + * * @param authorizationCode the AuthorizationCodeEntity to save * @return the saved AuthorizationCodeEntity */ @@ -38,7 +40,7 @@ public interface AuthorizationCodeRepository { /** * Get an authorization code from the repository by value. - * + * * @param code the authorization code value * @return the authentication associated with the code */ @@ -46,7 +48,7 @@ public interface AuthorizationCodeRepository { /** * Remove an authorization code from the repository - * + * * @param authorizationCodeEntity */ public void remove(AuthorizationCodeEntity authorizationCodeEntity); @@ -55,5 +57,11 @@ public interface AuthorizationCodeRepository { * @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 888daf0dce..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 d0593fbc3f..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,13 +17,14 @@ *******************************************************************************/ package org.mitre.oauth2.repository; -import java.util.Collection; 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.mitre.openid.connect.model.ApprovedSite; import org.mitre.uma.model.ResourceSet; public interface OAuth2TokenRepository { @@ -50,8 +52,10 @@ public interface OAuth2TokenRepository { public List getAccessTokensForClient(ClientDetailsEntity client); public List getRefreshTokensForClient(ClientDetailsEntity client); - - public OAuth2AccessTokenEntity getAccessTokenForIdToken(OAuth2AccessTokenEntity idToken); + + public Set getAccessTokensByUserName(String name); + + public Set getRefreshTokensByUserName(String name); public Set getAllAccessTokens(); @@ -59,8 +63,38 @@ public interface OAuth2TokenRepository { 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 d16cfa3d2f..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 aa2d83ca0a..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 index 70d1c67107..e0250a5035 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. 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 ed7f3a105b..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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); 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 487af45f37..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.service; @@ -33,18 +34,16 @@ public interface SystemScopeService { public static final String OFFLINE_ACCESS = "offline_access"; public static final String OPENID_SCOPE = "openid"; - public static final String ID_TOKEN_SCOPE = "id-token"; // ID tokens are generated using this scope 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(ID_TOKEN_SCOPE), - new SystemScope(REGISTRATION_TOKEN_SCOPE), - new SystemScope(RESOURCE_TOKEN_SCOPE) - ); + + public static final Set reservedScopes = + Sets.newHashSet( + new SystemScope(REGISTRATION_TOKEN_SCOPE), + new SystemScope(RESOURCE_TOKEN_SCOPE) + ); public Set getAll(); @@ -53,16 +52,16 @@ public interface SystemScopeService { * @return */ public Set getDefaults(); - + /** * 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 @@ -74,7 +73,7 @@ public interface SystemScopeService { * @return */ public Set getUnrestricted(); - + public SystemScope getById(Long id); public SystemScope getByValue(String value); @@ -98,23 +97,18 @@ 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-reserved or registered restricted scopes from the + * 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 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 7cbbafb699..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -24,6 +25,7 @@ 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; @@ -38,7 +40,7 @@ /** * Shim layer to convert a ClientDetails service into a UserDetails service - * + * * @author AANGANES * */ @@ -50,33 +52,37 @@ public class DefaultClientUserDetailsService implements UserDetailsService { @Autowired private ClientDetailsEntityService clientDetailsService; + @Autowired + private ConfigurationPropertiesBean config; + @Override public UserDetails loadUserByUsername(String clientId) throws UsernameNotFoundException { try { ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientId); - + if (client != null) { - + String password = Strings.nullToEmpty(client.getClientSecret()); - - if (client.getTokenEndpointAuthMethod() != null && + + 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))) { - + 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 - + 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); 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 index ee14c50557..64ef7e45cf 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -25,6 +24,7 @@ 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; @@ -40,9 +40,9 @@ /** * Loads client details based on URI encoding as passed in from basic auth. - * + * * Should only get called if non-encoded provider fails. - * + * * @author AANGANES * */ @@ -54,40 +54,44 @@ public class UriEncodedClientUserDetailsService implements UserDetailsService { @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.encodeQueryParam(Strings.nullToEmpty(client.getClientSecret()), "UTF-8"); - - if (client.getTokenEndpointAuthMethod() != null && - (client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) || - client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT))) { - + + 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 (UnsupportedEncodingException | InvalidClientException e) { + } catch (InvalidClientException e) { throw new UsernameNotFoundException("Client not found: " + clientId); } 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 486802be8e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,11 +16,20 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect; +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; @@ -32,19 +42,24 @@ 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; @@ -68,6 +83,9 @@ 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; @@ -75,30 +93,23 @@ 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; -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.getAsString; -import static org.mitre.util.JsonUtils.getAsStringSet; /** * 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 */ @@ -140,7 +151,7 @@ public static ClientDetailsEntity parse(JsonElement jsonEl) { 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 { @@ -148,6 +159,7 @@ public static ClientDetailsEntity parse(JsonElement jsonEl) { c.setJwks(jwks); } catch (ParseException e) { logger.error("Unable to parse JWK Set for client", e); + return null; } } @@ -193,6 +205,27 @@ public static ClientDetailsEntity parse(JsonElement jsonEl) { 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 { return null; @@ -223,7 +256,7 @@ public static RegisteredClient parseRegistered(JsonElement jsonEl) { rc.setClientSecretExpiresAt(getAsDate(o, CLIENT_SECRET_EXPIRES_AT)); rc.setSource(o); - + return rc; } else { return null; @@ -237,25 +270,25 @@ public static RegisteredClient parseRegistered(JsonElement jsonEl) { * @return */ public static JsonObject serialize(RegisteredClient c) { - + if (c.getSource() != null) { // if we have the original object, just use that return c.getSource(); } else { - + JsonObject o = new JsonObject(); - + 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) { @@ -264,14 +297,14 @@ public static JsonObject serialize(RegisteredClient c) { if (c.getRegistrationAccessToken() != null) { o.addProperty(REGISTRATION_ACCESS_TOKEN, c.getRegistrationAccessToken()); } - + 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()); @@ -285,7 +318,7 @@ public static JsonObject serialize(RegisteredClient c) { 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 @@ -294,7 +327,7 @@ public static JsonObject serialize(RegisteredClient c) { } 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()); @@ -313,6 +346,18 @@ public static JsonObject serialize(RegisteredClient c) { 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 index 316a378494..c351e228a4 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +14,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.config; @@ -28,17 +27,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.i18n.LocaleContext; import org.springframework.context.i18n.TimeZoneAwareLocaleContext; -import org.springframework.stereotype.Component; import org.springframework.web.servlet.i18n.AbstractLocaleContextResolver; /** - * + * * Resolve the server's locale from the injected ConfigurationPropertiesBean. - * + * * @author jricher * */ -@Component("localeResolver") public class ConfigurationBeanLocaleResolver extends AbstractLocaleContextResolver { @Autowired 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 746f510355..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,7 @@ *******************************************************************************/ package org.mitre.openid.connect.config; +import java.util.List; import java.util.Locale; import javax.annotation.PostConstruct; @@ -25,13 +27,16 @@ 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 * */ @@ -46,16 +51,26 @@ public class ConfigurationPropertiesBean { 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() { } @@ -65,7 +80,7 @@ public ConfigurationPropertiesBean() { * @throws HttpsUrlRequiredException */ @PostConstruct - public void checkForHttps() { + public void checkConfigConsistency() { if (!StringUtils.startsWithIgnoreCase(issuer, "https")) { if (this.forceHttps) { logger.error("Configured issuer url is not using https scheme. Server will be shut down!"); @@ -75,6 +90,10 @@ public void checkForHttps() { 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!"); + } } /** @@ -105,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 */ @@ -168,4 +198,79 @@ public Locale getLocale() { 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 index ddf2df2803..03c853498e 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -25,7 +24,7 @@ /** * Allows JWK Set strings to be used in XML configurations. - * + * * @author jricher * */ 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 6e7784828c..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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 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 6414490f26..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,219 +19,72 @@ import java.io.Serializable; -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 Address implements Serializable { - - 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; +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 3b829a37b7..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -20,7 +21,6 @@ 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,18 +30,12 @@ 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({ @@ -56,7 +50,7 @@ public class ApprovedSite { 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"; @@ -82,9 +76,6 @@ public class ApprovedSite { // this should include all information for what data to access private Set allowedScopes; - //Link to any access tokens approved through this stored decision - private Set approvedAccessTokens = Sets.newHashSet(); - /** * Empty constructor */ @@ -230,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 0cbdecc814..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.model; 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 2bbb959cc5..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -21,6 +22,7 @@ import java.io.ObjectOutputStream; import javax.persistence.Basic; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Convert; import javax.persistence.Entity; @@ -72,7 +74,7 @@ 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 @@ -369,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; @@ -379,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() @@ -417,13 +423,12 @@ public void setBirthdate(String birthdate) { @Override public JsonObject toJson() { - 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()); @@ -434,19 +439,19 @@ public JsonObject toJson() { obj.addProperty("picture", this.getPicture()); obj.addProperty("website", this.getWebsite()); obj.addProperty("gender", this.getGender()); - obj.addProperty("zone_info", this.getZoneinfo()); + obj.addProperty("zoneinfo", this.getZoneinfo()); obj.addProperty("locale", this.getLocale()); - obj.addProperty("updated_time", this.getUpdatedTime()); + 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()); @@ -454,10 +459,10 @@ public JsonObject toJson() { 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; @@ -486,9 +491,9 @@ public static UserInfo fromJson(JsonObject obj) { ui.setPicture(nullSafeGetString(obj, "picture")); ui.setWebsite(nullSafeGetString(obj, "website")); ui.setGender(nullSafeGetString(obj, "gender")); - ui.setZoneinfo(nullSafeGetString(obj, "zone_info")); + ui.setZoneinfo(nullSafeGetString(obj, "zoneinfo")); ui.setLocale(nullSafeGetString(obj, "locale")); - ui.setUpdatedTime(nullSafeGetString(obj, "updated_time")); + ui.setUpdatedTime(nullSafeGetString(obj, "updated_at")); ui.setBirthdate(nullSafeGetString(obj, "birthdate")); ui.setEmail(nullSafeGetString(obj, "email")); @@ -499,7 +504,7 @@ public static UserInfo fromJson(JsonObject obj) { 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(nullSafeGetString(addr, "formatted")); ui.getAddress().setStreetAddress(nullSafeGetString(addr, "street_address")); @@ -531,8 +536,8 @@ public JsonObject getSource() { 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; } @@ -732,26 +737,26 @@ 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(); - } - } + 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 65a71812ee..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -30,9 +31,9 @@ import com.nimbusds.jwt.JWTParser; /** - * + * * @author Michael Walsh, Justin Richer - * + * */ public class OIDCAuthenticationToken extends AbstractAuthenticationToken { @@ -49,9 +50,9 @@ public class OIDCAuthenticationToken extends AbstractAuthenticationToken { /** * 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 @@ -78,7 +79,7 @@ public OIDCAuthenticationToken(String subject, String issuer, /* * (non-Javadoc) - * + * * @see org.springframework.security.core.Authentication#getCredentials() */ @Override @@ -136,20 +137,20 @@ public UserInfo getUserInfo() { /* * 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); - } - } + 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 78dac57681..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.model; @@ -30,9 +31,9 @@ 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 * */ @@ -49,7 +50,7 @@ public class PairwiseIdentifier { public static final String PARAM_SECTOR_IDENTIFIER = "sectorIdentifier"; public static final String PARAM_SUB = "sub"; - + private Long id; private String identifier; private String 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 index 659daa3699..15201a8d4a 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -33,7 +32,7 @@ /** * AuthenticationToken for use as a data shuttle from the filter to the auth provider. - * + * * @author jricher * */ @@ -52,9 +51,9 @@ public class PendingOIDCAuthenticationToken extends AbstractAuthenticationToken /** * 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 @@ -80,7 +79,7 @@ public PendingOIDCAuthenticationToken (String subject, String issuer, /* * (non-Javadoc) - * + * * @see org.springframework.security.core.Authentication#getCredentials() */ @Override @@ -138,20 +137,20 @@ public String getIssuer() { /* * 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); - } - } + 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 47f13b7944..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -184,12 +185,12 @@ public interface UserInfo extends Serializable { public void setPhoneNumber(String phoneNumber); /** - * + * */ public Boolean getPhoneNumberVerified(); /** - * + * * @param phoneNumberVerified */ public void setPhoneNumberVerified(Boolean phoneNumberVerified); @@ -216,24 +217,24 @@ public interface UserInfo extends Serializable { /** - * + * * @return */ public String getBirthdate(); /** - * + * * @param birthdate */ public void setBirthdate(String birthdate); /** * Serialize this UserInfo object to JSON. - * + * * @return */ public JsonObject toJson(); - + /** * The JSON source of this UserInfo (if it was fetched), or null if it's local. * @return 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 fab6660056..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -50,7 +51,7 @@ 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"; 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 index 7f6016232f..78c33e8cdd 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -32,7 +31,7 @@ public class JsonObjectStringConverter implements AttributeConverter { private JsonParser parser = new JsonParser(); - + @Override public String convertToDatabaseColumn(JsonObject attribute) { if (attribute != 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 c4d09c0409..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -28,7 +29,7 @@ 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 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 fe57f44f93..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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 b541d8e2d0..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 0bbf4d4fff..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.repository; @@ -29,7 +30,7 @@ public interface PairwiseIdentifierRepository { /** * Get a pairwise identifier by its associated user subject and sector identifier. - * + * * @param sub * @param sectorIdentifierUri * @return @@ -38,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 02f91b2f04..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -20,7 +21,7 @@ /** * UserInfo repository interface - * + * * @author Michael Joseph Walsh * */ @@ -34,9 +35,9 @@ public interface UserInfoRepository { public UserInfo getByUsername(String username); /** - * + * * Get the UserInfo object by its email field - * + * * @param email * @return */ 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 d81314688e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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 d3cd7a72f7..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,16 +19,18 @@ 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.springframework.security.oauth2.provider.ClientDetails; /** * Interface for ApprovedSite service - * + * * @author Michael Joseph Walsh, aanganes - * + * */ public interface ApprovedSiteService { @@ -36,7 +39,7 @@ public interface ApprovedSiteService { /** * Return a collection of all ApprovedSites - * + * * @return the ApprovedSite collection, or null */ public Collection getAll(); @@ -44,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 @@ -53,7 +56,7 @@ public interface ApprovedSiteService { /** * Save an ApprovedSite - * + * * @param approvedSite * the ApprovedSite to be saved */ @@ -61,7 +64,7 @@ public interface ApprovedSiteService { /** * Get ApprovedSite for id - * + * * @param id * id for ApprovedSite * @return ApprovedSite for id, or null @@ -70,7 +73,7 @@ public interface ApprovedSiteService { /** * Remove the ApprovedSite - * + * * @param approvedSite * the ApprovedSite to remove */ @@ -101,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 15bdc54feb..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * 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 index 7067950127..ce85762799 100644 --- 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 @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -33,6 +34,7 @@ public interface MITREidDataService { 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"; @@ -46,7 +48,7 @@ public interface MITREidDataService { /** * Write out the current server state to the given JSON writer as a JSON object - * + * * @param writer * @throws IOException */ @@ -54,9 +56,18 @@ public interface MITREidDataService { /** * 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 859a66d7a1..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -22,9 +23,11 @@ import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.springframework.security.oauth2.provider.OAuth2Request; +import com.nimbusds.jwt.JWT; + /** * Service to create specialty OpenID Connect tokens. - * + * * @author Amanda Anganes * */ @@ -32,7 +35,7 @@ public interface OIDCTokenService { /** * Create an id token with the information provided. - * + * * @param client * @param request * @param issueTime @@ -41,13 +44,13 @@ public interface OIDCTokenService { * @param accessToken * @return */ - public OAuth2AccessTokenEntity createIdToken( + public JWT createIdToken( ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken); /** * Create a registration access token for the given client. - * + * * @param client * @return */ @@ -55,7 +58,7 @@ public OAuth2AccessTokenEntity createIdToken( /** * Create a resource access token for the given client (protected resource). - * + * * @param client * @return */ 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 3d4c14d15e..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; @@ -30,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 755eb8fdc8..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; @@ -31,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 0d92b3bdd3..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,12 +16,14 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service; import java.util.Map; +import org.mitre.openid.connect.model.ClientStat; + /** * @author jricher * @@ -32,25 +35,18 @@ public interface StatsService { * approvalCount: total approved sites * userCount: unique users * clientCount: unique clients - * + * * @return */ public Map getSummaryStats(); - /** - * Calculate usage count for all clients - * - * @return a map of id of client object to number of approvals - */ - public Map getByClientId(); - /** * Calculate the usage count for a single client - * - * @param id the id of the client to search on + * + * @param clientId the id of the client to search on * @return */ - public Integer getCountForClientId(Long id); + public ClientStat getCountForClientId(String clientId); /** * Trigger the stats to be recalculated upon next update. 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 8999235333..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -20,9 +21,9 @@ /** * Interface for UserInfo service - * + * * @author Michael Joseph Walsh - * + * */ public interface UserInfoService { @@ -45,8 +46,8 @@ public interface UserInfoService { public UserInfo getByUsernameAndClientId(String username, String clientId); /** - * Get the user registered at this server with the given email address. - * + * Get the user registered at this server with the given email address. + * * @param email * @return */ 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 8c1156f415..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -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,17 +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); - + /** * Removes the given WhitelistedSite from the repository - * + * * @param address * the WhitelistedSite object to remove */ @@ -64,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/JWKSetView.java b/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JWKSetView.java index db599ff60e..f18deaee18 100644 --- a/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JWKSetView.java +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/view/JWKSetView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; 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 64fc23aafb..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; @@ -28,6 +29,8 @@ 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.security.core.context.SecurityContextHolder; @@ -42,24 +45,26 @@ /** * 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 (required = false) private UserInfoService userInfoService; + private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl(); + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { @@ -69,31 +74,33 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons request.setAttribute("userAuthorities", gson.toJson(auth.getAuthorities())); } - 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()); + 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 { + request.setAttribute("userInfo", null); + request.setAttribute("userInfoJson", "null"); + } } else { - 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) { + // 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(auth.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) { - request.setAttribute("userInfo", user); - request.setAttribute("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 index 856617a123..d93e99c9c7 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -51,7 +50,7 @@ public class Claim { private JsonElement value; private Set claimTokenFormat; private Set issuer; - + /** * @return the id */ @@ -81,7 +80,7 @@ public String getName() { public void setName(String name) { this.name = name; } - + /** * @return the friendlyName */ @@ -96,7 +95,7 @@ public String getFriendlyName() { public void setFriendlyName(String friendlyName) { this.friendlyName = friendlyName; } - + /** * @return the claimType */ @@ -111,16 +110,16 @@ public String getClaimType() { 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") - ) + name = "claim_token_format", + joinColumns = @JoinColumn(name = "owner_id") + ) public Set getClaimTokenFormat() { return claimTokenFormat; } @@ -137,9 +136,9 @@ public void setClaimTokenFormat(Set claimTokenFormat) { @ElementCollection(fetch = FetchType.EAGER) @Column(name = "issuer") @CollectionTable( - name = "claim_issuer", - joinColumns = @JoinColumn(name = "owner_id") - ) + name = "claim_issuer", + joinColumns = @JoinColumn(name = "owner_id") + ) public Set getIssuer() { return issuer; } 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 index 07f035c7e1..0c00653307 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -21,7 +20,7 @@ /** * Data shuttle to return results of the claims processing service. - * + * * @author jricher * */ @@ -40,7 +39,7 @@ public ClaimProcessingResult(Collection unmatched) { this.unmatched = unmatched; this.matched = null; } - + /** * Create a matched result. isSatisfied is true. * @param matched @@ -92,5 +91,5 @@ public Policy getMatched() { 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 index 039e998dbd..42fd2d7f7f 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -67,27 +66,27 @@ public void setId(Long 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") - ) + name = "permission_scope", + joinColumns = @JoinColumn(name = "owner_id") + ) public Set getScopes() { return scopes; } - + /** * @param scopes the scopes to set */ 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 index bc3de80469..8b89ef5fda 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -39,9 +38,9 @@ import javax.persistence.TemporalType; /** - * + * * An UMA permission, used in the protection API. - * + * * @author jricher * */ @@ -57,16 +56,16 @@ 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 */ @@ -76,14 +75,14 @@ public class PermissionTicket { public Long getId() { return id; } - + /** * @param id the id to set */ public void setId(Long id) { this.id = id; } - + /** * @return the permission */ @@ -108,7 +107,7 @@ public void setPermission(Permission permission) { public String getTicket() { return ticket; } - + /** * @param ticket the ticket to set */ @@ -141,7 +140,7 @@ public void setExpiration(Date expiration) { name = "claim_to_permission_ticket", joinColumns = @JoinColumn(name = "permission_ticket_id"), inverseJoinColumns = @JoinColumn(name = "claim_id") - ) + ) public Collection getClaimsSupplied() { return claimsSupplied; } @@ -152,6 +151,6 @@ public Collection getClaimsSupplied() { 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 index b96da6bf72..32098e2fb1 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -37,7 +36,7 @@ /** * A set of claims required to fulfill a given permission. - * + * * @author jricher * */ @@ -49,7 +48,7 @@ public class Policy { private String name; private Collection claimsRequired; private Set scopes; - + /** * @return the id */ @@ -59,14 +58,14 @@ public class Policy { public Long getId() { return id; } - + /** * @param id the id to set */ public void setId(Long id) { this.id = id; } - + /** * @return the name */ @@ -91,7 +90,7 @@ public void setName(String name) { name = "claim_to_policy", joinColumns = @JoinColumn(name = "policy_id"), inverseJoinColumns = @JoinColumn(name = "claim_id") - ) + ) public Collection getClaimsRequired() { return claimsRequired; } @@ -102,20 +101,20 @@ public Collection getClaimsRequired() { 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") - ) + name = "policy_scope", + joinColumns = @JoinColumn(name = "owner_id") + ) public Set getScopes() { return scopes; } - + /** * @param scopes the scopes to set */ @@ -190,5 +189,5 @@ public boolean equals(Object obj) { } 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 index 78fca51b45..6303d377f2 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -39,10 +38,10 @@ @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") + @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 { @@ -60,12 +59,12 @@ public class ResourceSet { 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 */ @@ -75,7 +74,7 @@ public class ResourceSet { public Long getId() { return id; } - + /** * @param id the id to set */ @@ -91,14 +90,14 @@ public void setId(Long id) { public String getName() { return name; } - + /** * @param name the name to set */ public void setName(String name) { this.name = name; } - + /** * @return the uri */ @@ -107,14 +106,14 @@ public void setName(String name) { public String getUri() { return uri; } - + /** * @param uri the uri to set */ public void setUri(String uri) { this.uri = uri; } - + /** * @return the type */ @@ -123,34 +122,34 @@ public void setUri(String uri) { 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") - ) + 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 */ @@ -159,14 +158,14 @@ public void setScopes(Set scopes) { public String getIconUri() { return iconUri; } - + /** * @param iconUri the iconUri to set */ public void setIconUri(String iconUri) { this.iconUri = iconUri; } - + /** * @return the owner */ @@ -175,7 +174,7 @@ public void setIconUri(String iconUri) { public String getOwner() { return owner; } - + /** * @param owner the owner to set */ @@ -322,9 +321,9 @@ public boolean equals(Object obj) { } 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 index d7d2db3374..4b0ed95551 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -40,7 +39,7 @@ public class SavedRegisteredClient { private Long id; private String issuer; private RegisteredClient registeredClient; - + /** * @return the id */ @@ -52,7 +51,7 @@ public Long getId() { } /** - * + * * @param id the id to set */ public void setId(Long id) { @@ -93,5 +92,5 @@ public void setRegisteredClient(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 index 90ae886904..6a68f9da39 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -42,7 +41,7 @@ public String convertToDatabaseColumn(RegisteredClient attribute) { } else { return attribute.getSource().toString(); } - + } /* (non-Javadoc) 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 index de91598794..825453dcb6 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -30,9 +29,9 @@ public interface PermissionRepository { /** - * + * * Save a permission ticket. - * + * * @param p * @return */ @@ -40,7 +39,7 @@ public interface PermissionRepository { /** * Get the permission indicated by its ticket value. - * + * * @param ticket * @return */ @@ -48,22 +47,22 @@ public interface PermissionRepository { /** * 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 + * @return */ public Permission saveRawPermission(Permission p); /** * Get a permission object by its ID (used by the import/export API) - * + * * @param permissionId * @return */ @@ -71,7 +70,7 @@ public interface PermissionRepository { /** * Get all permission tickets issued against a resource set (called when RS is deleted) - * + * * @param rs * @return */ @@ -79,7 +78,7 @@ public interface PermissionRepository { /** * 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 index 997e2f15ba..7c5a1cb73b 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. 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 index 1d05b8c7e0..da88c4a7c4 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -22,20 +21,20 @@ 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 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 index de17e35581..ab7ea2e3f6 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -39,19 +38,19 @@ public interface PermissionService { 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. - * + * Save the updated permission ticket to the database. Does not create a new ticket. + * * @param ticket - * @return + * @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 index fd64e83cff..8da2ce017c 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -22,9 +21,9 @@ import org.mitre.uma.model.ResourceSet; /** - * + * * Manage registered resource sets at this authorization server. - * + * * @author jricher * */ 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 index 88c6f92b65..8ee6c86b15 100644 --- 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 @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -24,15 +23,15 @@ /** * Service to create special tokens for UMA. - * + * * @author jricher * */ public interface UmaTokenService { /** - * Create the RPT from the given authentication and ticket. - * + * 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 index 35136a9f38..b17f150be6 100644 --- a/openid-connect-common/src/main/java/org/mitre/util/JsonUtils.java +++ b/openid-connect-common/src/main/java/org/mitre/util/JsonUtils.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.util; @@ -28,6 +29,7 @@ import java.util.Map; import java.util.Set; +import org.mitre.oauth2.model.PKCEAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +37,7 @@ 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; @@ -46,7 +49,7 @@ /** * A collection of null-safe converters from common classes and JSON elements, using GSON. - * + * * @author jricher * */ @@ -61,12 +64,28 @@ public class JsonUtils { private static Gson gson = new Gson(); /** - * Translate a set of strings to a JSON array + * Translate a set of strings to a JSON array, empty array returned as null * @param value * @return */ public static JsonElement getAsArray(Set value) { - return gson.toJsonTree(value, new TypeToken>(){}.getType()); + 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()); + } } /** @@ -121,6 +140,21 @@ public static JWSAlgorithm getAsJwsAlgorithm(JsonObject o, String member) { } } + /** + * 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 */ @@ -152,7 +186,7 @@ public static Boolean getAsBoolean(JsonObject o, String member) { return null; } } - + /** * Gets the value of the given member as a Long, null if it doesn't exist */ @@ -163,7 +197,7 @@ public static Long getAsLong(JsonObject o, String member) { return e.getAsLong(); } else { return null; - } + } } else { return null; } @@ -254,19 +288,19 @@ public static Map readMap(JsonReader reader) throws IOException { 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; + 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); } @@ -278,21 +312,21 @@ 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; + 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; 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 835e4be1d5..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -19,6 +20,9 @@ import java.util.List; import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; + +import org.mitre.data.PageCriteria; /** * @author mfranklin @@ -28,15 +32,34 @@ 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) { T tmp = entityManager.merge(entity); entityManager.flush(); 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 edde15cc0d..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. 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 index 611d57ad7d..870e1bb3be 100644 --- a/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java +++ b/openid-connect-common/src/test/java/org/mitre/jose/TestJWKSetKeyStore.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -50,36 +49,36 @@ 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 + "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); + "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 + "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); + "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<>(); @@ -160,37 +159,6 @@ public void ksEmptyConstructorkLoc() { File file = new File(ks_file); - /* First, test with file without "read" permission */ - - boolean set = false; - - if (file.exists()) { - set = file.setReadable(false); - } - - // skip this part of the test on systems that don't allow the settable function, like Windows - if (set) { - - Resource loc_noread = new FileSystemResource(file); - assertTrue(loc_noread.exists()); - // assertTrue(!loc_noread.isReadable()); - - boolean thrown = false; - try { - ks.setLocation(loc_noread); - } catch (IllegalArgumentException e) { - thrown = true; - } - assertTrue(thrown); - - /* Now, make cache file readable */ - - if (file.exists()) { - file.setReadable(true); - } - - } - Resource loc = new FileSystemResource(file); assertTrue(loc.exists()); assertTrue(loc.isReadable()); 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 index c85e17f72f..18134b411f 100644 --- 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 @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -23,9 +24,16 @@ 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; @@ -33,6 +41,7 @@ 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; @@ -42,7 +51,6 @@ import com.nimbusds.jose.util.JSONObjectUtils; import com.nimbusds.jwt.EncryptedJWT; import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; import static org.hamcrest.CoreMatchers.nullValue; @@ -59,13 +67,18 @@ 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 = new JWTClaimsSet(); + private JWTClaimsSet claimsSet = null; + + @Rule + public ExpectedException exception = ExpectedException.none(); - // Example data taken from Mike Jones's draft-ietf-jose-json-web-encryption-14 appendix examples + // Example data taken from rfc7516 appendix A private String compactSerializedJwe = "eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ." + "OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGe" + "ipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDb" + @@ -81,41 +94,41 @@ public class TestDefaultJWTEncryptionAndDecryptionService { 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 + "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); + "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 + "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); + "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"), + private JWK AESjwk = new OctetSequenceKey(new Base64URL("GawgguFyGrWKav7AX4VKUg"), KeyUse.ENCRYPTION, null, JWEAlgorithm.A128KW, - AESkid, null, null, null); + AESkid, null, null, null, null, null); private Map keys = new ImmutableMap.Builder() @@ -152,8 +165,10 @@ public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException, service_3 = new DefaultJWTEncryptionAndDecryptionService(keys_3); service_4 = new DefaultJWTEncryptionAndDecryptionService(keys_4); - claimsSet.setIssuer(issuer); - claimsSet.setSubject(subject); + claimsSet = new JWTClaimsSet.Builder() + .issuer(issuer) + .subject(subject) + .build(); // Key Store @@ -167,7 +182,11 @@ public void prepare() throws NoSuchAlgorithmException, InvalidKeySpecException, @Test - public void decrypt_RSA() throws ParseException { + 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); @@ -184,7 +203,11 @@ public void decrypt_RSA() throws ParseException { @Test - public void encryptThenDecrypt_RSA() throws ParseException { + 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); @@ -203,7 +226,7 @@ public void encryptThenDecrypt_RSA() throws ParseException { assertThat(encryptedJwt.getJWTClaimsSet(), nullValue()); service.decryptJwt(encryptedJwt); - ReadOnlyJWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); + JWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); assertEquals(claimsSet.getIssuer(), resultClaims.getIssuer()); assertEquals(claimsSet.getSubject(), resultClaims.getSubject()); @@ -212,7 +235,11 @@ public void encryptThenDecrypt_RSA() throws ParseException { // The same as encryptThenDecrypt_RSA() but relies on the key from the map @Test - public void encryptThenDecrypt_nullID() throws ParseException { + 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); @@ -231,15 +258,21 @@ public void encryptThenDecrypt_nullID() throws ParseException { assertThat(encryptedJwt.getJWTClaimsSet(), nullValue()); service.decryptJwt(encryptedJwt); - ReadOnlyJWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); + JWTClaimsSet resultClaims = encryptedJwt.getJWTClaimsSet(); assertEquals(claimsSet.getIssuer(), resultClaims.getIssuer()); assertEquals(claimsSet.getSubject(), resultClaims.getSubject()); } - @Test(expected=IllegalStateException.class) - public void encrypt_nullID_oneKey() { + @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()); @@ -253,8 +286,15 @@ public void encrypt_nullID_oneKey() { } - @Test(expected=IllegalStateException.class) - public void decrypt_nullID() throws ParseException { + @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); 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 dbeca24cff..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; 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 f01d0a604f..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.model; 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 02a2fd79df..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect; 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 9ebeb8ca8c..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.config; @@ -63,7 +64,7 @@ public void testCheckForHttpsIssuerHttpDefaultFlag() { // leave as default, which is unset/false try { bean.setIssuer("http://localhost:8080/openid-connect-server/"); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for http issuer with default forceHttps, message:" + e.getMessage()); } @@ -77,7 +78,7 @@ public void testCheckForHttpsIssuerHttpFalseFlag() { try { bean.setIssuer("http://localhost:8080/openid-connect-server/"); bean.setForceHttps(false); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for http issuer with forceHttps=false, message:" + e.getMessage()); } @@ -90,7 +91,7 @@ public void testCheckForHttpsIssuerHttpTrueFlag() { // set to true bean.setIssuer("http://localhost:8080/openid-connect-server/"); bean.setForceHttps(true); - bean.checkForHttps(); + bean.checkConfigConsistency(); } @Test @@ -100,7 +101,7 @@ public void testCheckForHttpsIssuerHttpsDefaultFlag() { // leave as default, which is unset/false try { bean.setIssuer("https://localhost:8080/openid-connect-server/"); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for https issuer with default forceHttps, message:" + e.getMessage()); } @@ -114,7 +115,7 @@ public void testCheckForHttpsIssuerHttpsFalseFlag() { try { bean.setIssuer("https://localhost:8080/openid-connect-server/"); bean.setForceHttps(false); - bean.checkForHttps(); + bean.checkConfigConsistency(); } catch (BeanCreationException e) { fail("Unexpected BeanCreationException for https issuer with forceHttps=false, message:" + e.getMessage()); } @@ -128,11 +129,20 @@ public void testCheckForHttpsIssuerHttpsTrueFlag() { try { bean.setIssuer("https://localhost:8080/openid-connect-server/"); bean.setForceHttps(true); - bean.checkForHttps(); + 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 dfbd07da66..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,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.config; diff --git a/openid-connect-server-webapp/pom.xml b/openid-connect-server-webapp/pom.xml index dc451168a1..a294b1d8bb 100644 --- a/openid-connect-server-webapp/pom.xml +++ b/openid-connect-server-webapp/pom.xml @@ -1,26 +1,27 @@ + 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 org.mitre openid-connect-parent - 1.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT openid-connect-server-webapp war @@ -40,25 +41,25 @@ maven-war-plugin openid-connect-server-webapp - - gif - ico - jpg - png - pdf - eot - woff - ttf - svg - jwks - json - src/main/webapp true + + **/*.tag + **/*.jsp + + + + src/main/webapp + false + + **/*.tag + **/*.jsp + + less/** @@ -78,21 +79,40 @@ 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 @@ -125,7 +145,8 @@ com.zaxxer - HikariCP-java6 + 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 91% 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 df141d22a1..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 @@ -10,8 +10,8 @@ CREATE TABLE IF NOT EXISTS access_token ( refresh_token_id BIGINT, 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 ( @@ -131,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), @@ -169,6 +170,12 @@ CREATE TABLE IF NOT EXISTS client_details ( 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) ); @@ -197,6 +204,11 @@ 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), @@ -227,8 +239,6 @@ CREATE TABLE IF NOT EXISTS system_scope ( icon VARCHAR(256), 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) ); @@ -351,3 +361,24 @@ CREATE TABLE IF NOT EXISTS saved_registered_client ( 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 95% 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 1d3908ed11..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 @@ -69,7 +69,5 @@ CREATE TEMPORARY TABLE IF NOT EXISTS system_scope_TEMP ( description VARCHAR(4096), icon VARCHAR(256), restricted BOOLEAN, - default_scope BOOLEAN, - structured BOOLEAN, - structured_param_description VARCHAR(256) + 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 91% 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 c3a74d1dd2..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 @@ -10,7 +10,6 @@ CREATE TABLE IF NOT EXISTS access_token ( refresh_token_id BIGINT, client_id BIGINT, auth_holder_id BIGINT, - id_token_id BIGINT, approved_site_id BIGINT ); @@ -131,6 +130,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), @@ -169,6 +169,12 @@ CREATE TABLE IF NOT EXISTS client_details ( 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) ); @@ -197,6 +203,11 @@ 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), @@ -227,8 +238,6 @@ CREATE TABLE IF NOT EXISTS system_scope ( icon VARCHAR(256), 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) ); @@ -351,3 +360,24 @@ CREATE TABLE IF NOT EXISTS saved_registered_client ( 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/tables/psql_database_tables.sql b/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql similarity index 83% rename from openid-connect-server-webapp/src/main/resources/db/tables/psql_database_tables.sql rename to openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql index eef380b035..be871b7e80 100644 --- a/openid-connect-server-webapp/src/main/resources/db/tables/psql_database_tables.sql +++ b/openid-connect-server-webapp/src/main/resources/db/psql/psql_database_tables.sql @@ -3,15 +3,15 @@ -- CREATE TABLE IF NOT EXISTS access_token ( - id SERIAL PRIMARY KEY, + 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, - id_token_id BIGINT, - approved_site_id BIGINT + approved_site_id BIGINT, + UNIQUE(token_value) ); CREATE TABLE IF NOT EXISTS access_token_permissions ( @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS access_token_permissions ( ); CREATE TABLE IF NOT EXISTS address ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, formatted VARCHAR(256), street_address VARCHAR(256), locality VARCHAR(256), @@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS address ( ); CREATE TABLE IF NOT EXISTS approved_site ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, user_id VARCHAR(256), client_id VARCHAR(256), creation_date TIMESTAMP, @@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS approved_site_scope ( ); CREATE TABLE IF NOT EXISTS authentication_holder ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, user_auth_id BIGINT, approved BOOLEAN, redirect_uri VARCHAR(2048), @@ -85,7 +85,7 @@ CREATE TABLE IF NOT EXISTS authentication_holder_request_parameter ( ); CREATE TABLE IF NOT EXISTS saved_user_auth ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name VARCHAR(1024), authenticated BOOLEAN, source_class VARCHAR(2048) @@ -102,7 +102,7 @@ CREATE TABLE IF NOT EXISTS client_authority ( ); CREATE TABLE IF NOT EXISTS authorization_code ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, code VARCHAR(256), auth_holder_id BIGINT, expiration TIMESTAMP @@ -119,29 +119,30 @@ CREATE TABLE IF NOT EXISTS client_response_type ( ); CREATE TABLE IF NOT EXISTS blacklisted_site ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, uri VARCHAR(2048) ); CREATE TABLE IF NOT EXISTS client_details ( - id SERIAL PRIMARY KEY, + 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), @@ -150,25 +151,31 @@ CREATE TABLE IF NOT EXISTS client_details ( 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) ); @@ -193,12 +200,17 @@ CREATE TABLE IF NOT EXISTS client_contact ( ); CREATE TABLE IF NOT EXISTS client_redirect_uri ( - owner_id BIGINT, - redirect_uri VARCHAR(2048) + 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 SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, token_value VARCHAR(4096), expiration TIMESTAMP, auth_holder_id BIGINT, @@ -206,8 +218,8 @@ CREATE TABLE IF NOT EXISTS refresh_token ( ); CREATE TABLE IF NOT EXISTS client_resource ( - owner_id BIGINT, - resource_id VARCHAR(256) + owner_id BIGINT, + resource_id VARCHAR(256) ); CREATE TABLE IF NOT EXISTS client_scope ( @@ -221,19 +233,17 @@ CREATE TABLE IF NOT EXISTS token_scope ( ); CREATE TABLE IF NOT EXISTS system_scope ( - id SERIAL PRIMARY KEY, + 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, - structured BOOLEAN DEFAULT false NOT NULL, - structured_param_description VARCHAR(256), UNIQUE (scope) ); CREATE TABLE IF NOT EXISTS user_info ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, sub VARCHAR(256), preferred_username VARCHAR(256), name VARCHAR(256), @@ -258,7 +268,7 @@ CREATE TABLE IF NOT EXISTS user_info ( ); CREATE TABLE IF NOT EXISTS whitelisted_site ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, creator_user_id VARCHAR(256), client_id VARCHAR(256) ); @@ -269,14 +279,14 @@ CREATE TABLE IF NOT EXISTS whitelisted_site_scope ( ); CREATE TABLE IF NOT EXISTS pairwise_identifier ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, identifier VARCHAR(256), sub VARCHAR(256), sector_identifier VARCHAR(2048) ); CREATE TABLE IF NOT EXISTS resource_set ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name VARCHAR(1024) NOT NULL, uri VARCHAR(1024), icon_uri VARCHAR(1024), @@ -291,14 +301,14 @@ CREATE TABLE IF NOT EXISTS resource_set_scope ( ); CREATE TABLE IF NOT EXISTS permission_ticket ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, ticket VARCHAR(256) NOT NULL, permission_id BIGINT NOT NULL, expiration TIMESTAMP ); CREATE TABLE IF NOT EXISTS permission ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, resource_set_id BIGINT ); @@ -308,7 +318,7 @@ CREATE TABLE IF NOT EXISTS permission_scope ( ); CREATE TABLE IF NOT EXISTS claim ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name VARCHAR(256), friendly_name VARCHAR(1024), claim_type VARCHAR(1024), @@ -326,7 +336,7 @@ CREATE TABLE IF NOT EXISTS claim_to_permission_ticket ( ); CREATE TABLE IF NOT EXISTS policy ( - id SERIAL PRIMARY KEY, + id BIGSERIAL PRIMARY KEY, name VARCHAR(1024), resource_set_id BIGINT ); @@ -347,7 +357,28 @@ CREATE TABLE IF NOT EXISTS claim_issuer ( ); CREATE TABLE IF NOT EXISTS saved_registered_client ( - id SERIAL PRIMARY KEY, + 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/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 fbdc94d60d..0000000000 --- a/openid-connect-server-webapp/src/main/resources/db/upgrade/hsql_1.0-to-1.1_upgrade.sql +++ /dev/null @@ -1,55 +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; - -DROP TABLE IF EXISTS authorities_TEMP; - -DROP TABLE IF EXISTS users_TEMP; - -DROP TABLE IF EXISTS user_info_TEMP; - -DROP TABLE IF EXISTS client_details_TEMP; - -DROP TABLE IF EXISTS client_scope_TEMP; - -DROP TABLE IF EXISTS client_redirect_uri_TEMP; - -DROP TABLE IF EXISTS client_grant_type_TEMP; - -DROP TABLE IF EXISTS system_scope_TEMP; - - - - -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 40f24468f1..0000000000 --- a/openid-connect-server-webapp/src/main/resources/db/upgrade/mysql_1.0-to-1.1_upgrade.sql +++ /dev/null @@ -1,51 +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; - -DROP TEMPORARY TABLE IF EXISTS authorities_TEMP; - -DROP TEMPORARY TABLE IF EXISTS users_TEMP; - -DROP TEMPORARY TABLE IF EXISTS user_info_TEMP; - -DROP TEMPORARY TABLE IF EXISTS client_details_TEMP; - -DROP TEMPORARY TABLE IF EXISTS client_scope_TEMP; - -DROP TEMPORARY TABLE IF EXISTS client_redirect_uri_TEMP; - -DROP TEMPORARY TABLE IF EXISTS client_grant_type_TEMP; - -DROP TEMPORARY TABLE IF EXISTS system_scope_TEMP; - - -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/log4j.xml b/openid-connect-server-webapp/src/main/resources/log4j.xml index 954c300a4b..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. + --> @@ -45,6 +46,12 @@ + + + + + + @@ -63,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 dbdf1f7f4c..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"> @@ -44,10 +45,41 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -70,7 +102,7 @@ - + + + + + @@ -110,6 +146,7 @@ + @@ -117,17 +154,34 @@ + + + + + + + + + + + + + - + + - + + @@ -160,6 +216,10 @@ + + + + @@ -186,9 +246,15 @@ + + - - + + + + + + @@ -198,12 +264,13 @@ - - - + + + + @@ -243,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 index 308f347c18..0c5e5019f8 100644 --- 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 @@ -1,20 +1,19 @@ + 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. + --> + 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/crypto-config.xml b/openid-connect-server-webapp/src/main/webapp/WEB-INF/crypto-config.xml index 99bbd918bf..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,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/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 c1c8620dbd..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,25 +1,26 @@ + 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"> @@ -30,16 +31,16 @@ + If you are using a file based HSQLDB you should not run this every time. --> - + - + - - - - + + + + @@ -48,35 +49,80 @@ - + + + + + + - - - - + + + + - --> + + + + + + + + + + src/main/resources/db/psql/psql_database_tables.sql --> + + + + + + + + + + + + + + + + + + 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 index 2ac96ba9d8..afe40844af 100644 --- 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 @@ -1,20 +1,19 @@ + 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. + --> + 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"> + @@ -46,6 +46,9 @@ + + + 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 d45a8ee184..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,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/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 b17a1496a0..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,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/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"> @@ -43,12 +44,34 @@ - + + + + + + + + + + + + + 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 703dc39042..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,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/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 4d4ca8bb03..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/aboutContent.tag +++ /dev/null @@ -1,5 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -

-

- -

\ No newline at end of file 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 4f978c4d3f..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/contactContent.tag +++ /dev/null @@ -1,5 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -

-

- -

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 f15cab94dd..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,2 +1,4 @@ <%@ 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 a089cc2bbb..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 @@ -22,16 +22,24 @@ + - - - - - - - + + + + - +
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 e43a7c78c6..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 @@ -14,6 +14,7 @@ + @@ -35,12 +36,17 @@ - + 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 0d9a4e6ac4..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageAbout.tag +++ /dev/null @@ -1,6 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -

- -

- -

»

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 db9d3f51e5..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageContact.tag +++ /dev/null @@ -1,5 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -

-

- -

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 45e3527805..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageStats.tag +++ /dev/null @@ -1,27 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -

- -

- -

- - - -

- - \ No newline at end of file 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 851ddbe0b2..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/landingPageWelcome.tag +++ /dev/null @@ -1,9 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -
-
- -
-

-

-
-
\ No newline at end of file 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/statsContent.tag b/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/statsContent.tag deleted file mode 100644 index 9e529ff684..0000000000 --- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/tags/statsContent.tag +++ /dev/null @@ -1,8 +0,0 @@ -<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> -

    - -

    - - - -

    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 8cf0302614..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 @@ -34,52 +34,26 @@ - ${config.topbarTitle} + + + + ${config.shortTopbarTitle} + ${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 031b2133d6..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,25 +1,26 @@ + 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/task http://www.springframework.org/schema/task/spring-task.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> @@ -31,6 +32,7 @@ + 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 2ef983e25c..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,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/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,6 +52,7 @@ + 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 41adfc8b5e..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,3 +1,4 @@ +<%@ 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"%> @@ -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 80385cc35c..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,6 +1,7 @@ +<%@ 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"%> @@ -12,12 +13,12 @@
    - <% 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)) { %>
    ×

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

    <% } %> @@ -36,7 +37,7 @@
    + action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }authorize" method="post">
    @@ -84,7 +85,7 @@
    • - +
    @@ -103,7 +104,7 @@
    - +
    @@ -217,10 +218,6 @@ - - - - @@ -260,8 +257,8 @@ - - + +   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 159f664c27..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,3 +1,4 @@ +<%@ 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"%> @@ -14,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 index ce2d373e3d..66c5f585ed 100644 --- 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 @@ -1,3 +1,4 @@ +<%@ 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"%> @@ -16,8 +17,8 @@ if (request.getAttribute("error") != null && request.getAttribute("error") insta } 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()); - request.setAttribute("message", status.getReasonPhrase()); + 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 f3e9029044..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,3 +1,4 @@ +<%@ 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"%> @@ -11,15 +12,29 @@
    - +
    +
    + +
    +

    +

    +
    +
    - +

    + +

    + +

    »

    - +

    +

    + +

    @@ -27,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 64b03d2f17..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,8 +1,8 @@ +<%@ 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"%> - + + + + + + \ No newline at end of file diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/blacklist.html b/openid-connect-server-webapp/src/main/webapp/resources/template/blacklist.html new file mode 100644 index 0000000000..074fd0a0df --- /dev/null +++ b/openid-connect-server-webapp/src/main/webapp/resources/template/blacklist.html @@ -0,0 +1,68 @@ + + + + + diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/client.html b/openid-connect-server-webapp/src/main/webapp/resources/template/client.html index 6a61f38c44..2a748fefbe 100644 --- a/openid-connect-server-webapp/src/main/webapp/resources/template/client.html +++ b/openid-connect-server-webapp/src/main/webapp/resources/template/client.html @@ -1,6 +1,7 @@ + + diff --git a/openid-connect-server-webapp/src/main/webapp/resources/template/dynreg.html b/openid-connect-server-webapp/src/main/webapp/resources/template/dynreg.html index 19fab16b81..eda228006d 100644 --- a/openid-connect-server-webapp/src/main/webapp/resources/template/dynreg.html +++ b/openid-connect-server-webapp/src/main/webapp/resources/template/dynreg.html @@ -1,6 +1,7 @@ + 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-server @@ -22,7 +23,7 @@ org.mitre openid-connect-parent - 1.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT .. @@ -46,7 +47,36 @@ org.springframework spring-tx + + org.springframework + spring-orm + test + + + commons-logging + commons-logging + + + + + org.eclipse.persistence + org.eclipse.persistence.core + + + org.hsqldb + hsqldb + test + + + org.eclipse.persistence + org.eclipse.persistence.jpa + test + + + org.apache.commons + commons-io + OpenID Connect server libraries for Spring and Spring Security. diff --git a/openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java b/openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java index 09de0f044c..493f769d3a 100644 --- a/openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java +++ b/openid-connect-server/src/main/java/org/mitre/discovery/view/WebfingerView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.discovery.view; @@ -54,27 +55,27 @@ public class WebfingerView extends AbstractView { private static final Logger logger = LoggerFactory.getLogger(WebfingerView.class); private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; - } - return false; - } - - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } + + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { diff --git a/openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java b/openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java index cce124eb48..270a7649eb 100644 --- a/openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/discovery/web/DiscoveryEndpoint.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -24,7 +25,9 @@ import org.mitre.discovery.util.WebfingerURLNormalizer; import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.oauth2.model.PKCEAlgorithm; import org.mitre.oauth2.service.SystemScopeService; +import org.mitre.oauth2.web.DeviceEndpoint; import org.mitre.oauth2.web.IntrospectionEndpoint; import org.mitre.oauth2.web.RevocationEndpoint; import org.mitre.openid.connect.config.ConfigurationPropertiesBean; @@ -33,6 +36,7 @@ import org.mitre.openid.connect.view.HttpCodeView; import org.mitre.openid.connect.view.JsonEntityView; import org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint; +import org.mitre.openid.connect.web.EndSessionEndpoint; import org.mitre.openid.connect.web.JWKSetPublishingEndpoint; import org.mitre.openid.connect.web.UserInfoEndpoint; import org.slf4j.Logger; @@ -55,9 +59,9 @@ import com.nimbusds.jose.JWSAlgorithm; /** - * + * * Handle OpenID Connect Discovery. - * + * * @author jricher * */ @@ -101,9 +105,12 @@ public String apply(Algorithm alg) { } }; - @RequestMapping(value={"/" + WEBFINGER_URL}, - params={"resource", "rel=http://openid.net/specs/connect/1.0/issuer"}, produces = MediaType.APPLICATION_JSON_VALUE) - public String webfinger(@RequestParam("resource") String resource, Model model) { + @RequestMapping(value={"/" + WEBFINGER_URL}, produces = MediaType.APPLICATION_JSON_VALUE) + public String webfinger(@RequestParam("resource") String resource, @RequestParam(value = "rel", required = false) String rel, Model model) { + + if (!Strings.isNullOrEmpty(rel) && !rel.equals("http://openid.net/specs/connect/1.0/issuer")) { + logger.warn("Responding to webfinger request for non-OIDC relation: " + rel); + } if (!resource.equals(config.getIssuer())) { // it's not the issuer directly, need to check other methods @@ -116,12 +123,12 @@ public String webfinger(@RequestParam("resource") String resource, Model model) // check on email addresses first UserInfo user = userService.getByEmailAddress(resourceUri.getUserInfo() + "@" + resourceUri.getHost()); - + if (user == null) { // user wasn't found, see if the local part of the username matches, plus our issuer host - + user = userService.getByUsername(resourceUri.getUserInfo()); // first part is the username - + if (user != null) { // username matched, check the host component UriComponents issuerComponents = UriComponentsBuilder.fromHttpUrl(config.getIssuer()).build(); @@ -131,16 +138,16 @@ public String webfinger(@RequestParam("resource") String resource, Model model) model.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + } else { - + // if the user's still null, punt and say we didn't find them - + logger.info("User not found: " + resource); model.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + } } else { @@ -282,22 +289,22 @@ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values String baseUrl = config.getIssuer(); if (!baseUrl.endsWith("/")) { - logger.warn("Configured issuer doesn't end in /, adding for discovery: " + baseUrl); + logger.debug("Configured issuer doesn't end in /, adding for discovery: {}", baseUrl); baseUrl = baseUrl.concat("/"); } - Collection serverSigningAlgs = signService.getAllSigningAlgsSupported(); - Collection clientSymmetricSigningAlgs = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512); - Collection clientSymmetricAndAsymmetricSigningAlgs = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512, - JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, - JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, + signService.getAllSigningAlgsSupported(); + Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512); + Collection clientSymmetricAndAsymmetricSigningAlgs = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512, + JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, + JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512); - Collection clientSymmetricAndAsymmetricSigningAlgsWithNone = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512, - JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, - JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, - JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512, + Collection clientSymmetricAndAsymmetricSigningAlgsWithNone = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512, + JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, + JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, + JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512, Algorithm.NONE); - ArrayList grantTypes = Lists.newArrayList("authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer", "client_credentials", "urn:ietf:params:oauth:grant_type:redelegate"); + ArrayList grantTypes = Lists.newArrayList("authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer", "client_credentials", "urn:ietf:params:oauth:grant_type:redelegate", "urn:ietf:params:oauth:grant-type:device_code","refresh_token"); Map m = new HashMap<>(); m.put("issuer", config.getIssuer()); @@ -305,7 +312,7 @@ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values m.put("token_endpoint", baseUrl + "token"); m.put("userinfo_endpoint", baseUrl + UserInfoEndpoint.URL); //check_session_iframe - //end_session_endpoint + m.put("end_session_endpoint", baseUrl + EndSessionEndpoint.URL); m.put("jwks_uri", baseUrl + JWKSetPublishingEndpoint.URL); m.put("registration_endpoint", baseUrl + DynamicClientRegistrationEndpoint.URL); m.put("scopes_supported", scopeService.toStrings(scopeService.getUnrestricted())); // these are the scopes that you can dynamically register for, which is what matters for discovery @@ -338,9 +345,9 @@ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values "picture", "website", "gender", - "zone_info", + "zoneinfo", "locale", - "updated_time", + "updated_at", "birthdate", "email", "email_verified", @@ -361,6 +368,10 @@ OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values m.put("introspection_endpoint", baseUrl + IntrospectionEndpoint.URL); // token introspection endpoint for verifying tokens m.put("revocation_endpoint", baseUrl + RevocationEndpoint.URL); // token revocation endpoint + m.put("code_challenge_methods_supported", Lists.newArrayList(PKCEAlgorithm.plain.getName(), PKCEAlgorithm.S256.getName())); + + m.put("device_authorization_endpoint", baseUrl + DeviceEndpoint.URL); + model.addAttribute(JsonEntityView.ENTITY, m); return JsonEntityView.VIEWNAME; diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/AssertionOAuth2RequestFactory.java b/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/AssertionOAuth2RequestFactory.java new file mode 100644 index 0000000000..e8c9465ff3 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/AssertionOAuth2RequestFactory.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.oauth2.assertion; + +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +import com.nimbusds.jwt.JWT; + +/** + * Take in an assertion and token request and generate an OAuth2Request from it, including scopes and other important components + * + * @author jricher + * + */ +public interface AssertionOAuth2RequestFactory { + + /** + * @param client + * @param tokenRequest + * @param assertion + * @return + */ + OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion); + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/impl/DirectCopyRequestFactory.java b/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/impl/DirectCopyRequestFactory.java new file mode 100644 index 0000000000..1b508dac4b --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/assertion/impl/DirectCopyRequestFactory.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * 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.assertion.impl; + +import java.text.ParseException; +import java.util.Set; + +import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.TokenRequest; + +import com.google.common.collect.Sets; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; + +/** + * Takes an assertion from a trusted source, looks for the fields: + * + * - scope, space-separated list of strings + * - aud, array of audience IDs + * + * @author jricher + * + */ +public class DirectCopyRequestFactory implements AssertionOAuth2RequestFactory { + + /* (non-Javadoc) + * @see org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory#createOAuth2Request(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest, com.nimbusds.jwt.JWT) + */ + @Override + public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest, JWT assertion) { + + try { + JWTClaimsSet claims = assertion.getJWTClaimsSet(); + Set scope = OAuth2Utils.parseParameterList(claims.getStringClaim("scope")); + + Set resources = Sets.newHashSet(claims.getAudience()); + + return new OAuth2Request(tokenRequest.getRequestParameters(), client.getClientId(), client.getAuthorities(), true, scope, resources, null, null, null); + } catch (ParseException e) { + return null; + } + + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/AuthorizationPendingException.java b/openid-connect-server/src/main/java/org/mitre/oauth2/exception/AuthorizationPendingException.java new file mode 100644 index 0000000000..c98f95cfc0 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/exception/AuthorizationPendingException.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.exception; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +/** + * @author jricher + * + */ +public class AuthorizationPendingException extends OAuth2Exception { + + /** + * @param msg + */ + public AuthorizationPendingException(String msg) { + super(msg); + } + + /** + * + */ + private static final long serialVersionUID = -7078098692596870940L; + + /* (non-Javadoc) + * @see org.springframework.security.oauth2.common.exceptions.OAuth2Exception#getOAuth2ErrorCode() + */ + @Override + public String getOAuth2ErrorCode() { + return "authorization_pending"; + } + + + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeExpiredException.java b/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeExpiredException.java new file mode 100644 index 0000000000..3194531f82 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DeviceCodeExpiredException.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.exception; + +import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; + +/** + * @author jricher + * + */ +public class DeviceCodeExpiredException extends OAuth2Exception { + + /** + * @param msg + */ + public DeviceCodeExpiredException(String msg) { + super(msg); + } + + /** + * + */ + private static final long serialVersionUID = -7078098692596870940L; + + /* (non-Javadoc) + * @see org.springframework.security.oauth2.common.exceptions.OAuth2Exception#getOAuth2ErrorCode() + */ + @Override + public String getOAuth2ErrorCode() { + return "expired_token"; + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java b/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java index 8230b3d0ba..52e90477e1 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/exception/DuplicateClientIdException.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -23,7 +24,7 @@ public DuplicateClientIdException(String clientId) { } /** - * + * */ private static final long serialVersionUID = 1L; diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java index 497ed6e231..269db62171 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthenticationHolderRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -22,6 +23,8 @@ import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; +import org.mitre.data.DefaultPageCriteria; +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.mitre.oauth2.repository.AuthenticationHolderRepository; import org.mitre.util.jpa.JpaUtil; @@ -29,12 +32,12 @@ import org.springframework.transaction.annotation.Transactional; @Repository -@Transactional +@Transactional(value="defaultTransactionManager") public class JpaAuthenticationHolderRepository implements AuthenticationHolderRepository { private static final int MAXEXPIREDRESULTS = 1000; - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; @Override @@ -49,7 +52,7 @@ public AuthenticationHolderEntity getById(Long id) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(AuthenticationHolderEntity a) { AuthenticationHolderEntity found = getById(a.getId()); if (found != null) { @@ -60,18 +63,23 @@ public void remove(AuthenticationHolderEntity a) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public AuthenticationHolderEntity save(AuthenticationHolderEntity a) { return JpaUtil.saveOrUpdate(a.getId(), manager, a); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public List getOrphanedAuthenticationHolders() { + DefaultPageCriteria pageCriteria = new DefaultPageCriteria(0,MAXEXPIREDRESULTS); + return getOrphanedAuthenticationHolders(pageCriteria); + } + + @Override + @Transactional(value="defaultTransactionManager") + public List getOrphanedAuthenticationHolders(PageCriteria pageCriteria) { TypedQuery query = manager.createNamedQuery(AuthenticationHolderEntity.QUERY_GET_UNUSED, AuthenticationHolderEntity.class); - query.setMaxResults(MAXEXPIREDRESULTS); - List unusedAuthenticationHolders = query.getResultList(); - return unusedAuthenticationHolders; + return JpaUtil.getResultPage(query, pageCriteria); } } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java index b7dd22e9ca..ad7788b6c0 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaAuthorizationCodeRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.repository.impl; @@ -26,6 +27,7 @@ import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; +import org.mitre.data.PageCriteria; import org.mitre.oauth2.model.AuthorizationCodeEntity; import org.mitre.oauth2.repository.AuthorizationCodeRepository; import org.mitre.util.jpa.JpaUtil; @@ -34,22 +36,22 @@ /** * JPA AuthorizationCodeRepository implementation. - * + * * @author aanganes * */ @Repository -@Transactional +@Transactional(value="defaultTransactionManager") public class JpaAuthorizationCodeRepository implements AuthorizationCodeRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") EntityManager manager; /* (non-Javadoc) * @see org.mitre.oauth2.repository.AuthorizationCodeRepository#save(org.mitre.oauth2.model.AuthorizationCodeEntity) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public AuthorizationCodeEntity save(AuthorizationCodeEntity authorizationCode) { return JpaUtil.saveOrUpdate(authorizationCode.getId(), manager, authorizationCode); @@ -60,7 +62,7 @@ public AuthorizationCodeEntity save(AuthorizationCodeEntity authorizationCode) { * @see org.mitre.oauth2.repository.AuthorizationCodeRepository#getByCode(java.lang.String) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public AuthorizationCodeEntity getByCode(String code) { TypedQuery query = manager.createNamedQuery(AuthorizationCodeEntity.QUERY_BY_VALUE, AuthorizationCodeEntity.class); query.setParameter("code", code); @@ -77,7 +79,7 @@ public void remove(AuthorizationCodeEntity authorizationCodeEntity) { AuthorizationCodeEntity found = manager.find(AuthorizationCodeEntity.class, authorizationCodeEntity.getId()); if (found != null) { manager.remove(found); - } + } } /* (non-Javadoc) @@ -89,7 +91,15 @@ public Collection getExpiredCodes() { query.setParameter(AuthorizationCodeEntity.PARAM_DATE, new Date()); // this gets anything that's already expired return query.getResultList(); } - - + + + @Override + public Collection getExpiredCodes(PageCriteria pageCriteria) { + TypedQuery query = manager.createNamedQuery(AuthorizationCodeEntity.QUERY_EXPIRATION_BY_DATE, AuthorizationCodeEntity.class); + query.setParameter(AuthorizationCodeEntity.PARAM_DATE, new Date()); // this gets anything that's already expired + return JpaUtil.getResultPage(query, pageCriteria); + } + + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaDeviceCodeRepository.java b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaDeviceCodeRepository.java new file mode 100644 index 0000000000..65b1f03b2f --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaDeviceCodeRepository.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.repository.impl; + +import static org.mitre.util.jpa.JpaUtil.getSingleResult; +import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; + +import java.util.Collection; +import java.util.Date; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; + +import org.mitre.oauth2.model.DeviceCode; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author jricher + * + */ +@Repository("jpaDeviceCodeRepository") +public class JpaDeviceCodeRepository implements DeviceCodeRepository { + + @PersistenceContext(unitName="defaultPersistenceUnit") + private EntityManager em; + + /* (non-Javadoc) + */ + @Override + @Transactional(value="defaultTransactionManager") + public DeviceCode getById(Long id) { + return em.find(DeviceCode.class, id); + } + + /* (non-Javadoc) + */ + @Override + @Transactional(value="defaultTransactionManager") + public DeviceCode getByUserCode(String value) { + TypedQuery query = em.createNamedQuery(DeviceCode.QUERY_BY_USER_CODE, DeviceCode.class); + query.setParameter(DeviceCode.PARAM_USER_CODE, value); + return getSingleResult(query.getResultList()); + } + + /* (non-Javadoc) + */ + @Override + @Transactional(value="defaultTransactionManager") + public DeviceCode getByDeviceCode(String value) { + TypedQuery query = em.createNamedQuery(DeviceCode.QUERY_BY_DEVICE_CODE, DeviceCode.class); + query.setParameter(DeviceCode.PARAM_DEVICE_CODE, value); + return getSingleResult(query.getResultList()); + } + + /* (non-Javadoc) + */ + @Override + @Transactional(value="defaultTransactionManager") + public void remove(DeviceCode scope) { + DeviceCode found = getById(scope.getId()); + + if (found != null) { + em.remove(found); + } + + } + + /* (non-Javadoc) + * @see org.mitre.oauth2.repository.SystemScopeRepository#save(org.mitre.oauth2.model.SystemScope) + */ + @Override + @Transactional(value="defaultTransactionManager") + public DeviceCode save(DeviceCode scope) { + return saveOrUpdate(scope.getId(), em, scope); + } + + /* (non-Javadoc) + * @see org.mitre.oauth2.repository.impl.DeviceCodeRepository#getExpiredCodes() + */ + @Override + @Transactional(value="defaultTransactionManager") + public Collection getExpiredCodes() { + TypedQuery query = em.createNamedQuery(DeviceCode.QUERY_EXPIRED_BY_DATE, DeviceCode.class); + query.setParameter(DeviceCode.PARAM_DATE, new Date()); + return query.getResultList(); + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java index ff80d43c42..58c0bf196f 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2ClientRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -33,10 +34,10 @@ * */ @Repository -@Transactional +@Transactional(value="defaultTransactionManager") public class JpaOAuth2ClientRepository implements OAuth2ClientRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; public JpaOAuth2ClientRepository() { diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java index 0e865fe09f..718a233572 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaOAuth2TokenRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,21 +18,32 @@ package org.mitre.oauth2.repository.impl; import java.text.ParseException; +import java.util.ArrayList; import java.util.Date; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import javax.persistence.Query; import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaDelete; +import javax.persistence.criteria.Root; +import org.mitre.data.DefaultPageCriteria; +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.mitre.oauth2.repository.OAuth2TokenRepository; +import org.mitre.openid.connect.model.ApprovedSite; import org.mitre.uma.model.ResourceSet; import org.mitre.util.jpa.JpaUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -43,7 +55,9 @@ public class JpaOAuth2TokenRepository implements OAuth2TokenRepository { private static final int MAXEXPIREDRESULTS = 1000; - @PersistenceContext + private static final Logger logger = LoggerFactory.getLogger(JpaOAuth2TokenRepository.class); + + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; @Override @@ -77,15 +91,15 @@ public OAuth2AccessTokenEntity getAccessTokenById(Long id) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity token) { return JpaUtil.saveOrUpdate(token.getId(), manager, token); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void removeAccessToken(OAuth2AccessTokenEntity accessToken) { - OAuth2AccessTokenEntity found = getAccessTokenByValue(accessToken.getValue()); + OAuth2AccessTokenEntity found = getAccessTokenById(accessToken.getId()); if (found != null) { manager.remove(found); } else { @@ -94,7 +108,7 @@ public void removeAccessToken(OAuth2AccessTokenEntity accessToken) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void clearAccessTokensForRefreshToken(OAuth2RefreshTokenEntity refreshToken) { TypedQuery query = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_REFRESH_TOKEN, OAuth2AccessTokenEntity.class); query.setParameter(OAuth2AccessTokenEntity.PARAM_REFERSH_TOKEN, refreshToken); @@ -122,15 +136,15 @@ public OAuth2RefreshTokenEntity getRefreshTokenById(Long id) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken) { return JpaUtil.saveOrUpdate(refreshToken.getId(), manager, refreshToken); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken) { - OAuth2RefreshTokenEntity found = getRefreshTokenByValue(refreshToken.getValue()); + OAuth2RefreshTokenEntity found = getRefreshTokenById(refreshToken.getId()); if (found != null) { manager.remove(found); } else { @@ -139,7 +153,7 @@ public void removeRefreshToken(OAuth2RefreshTokenEntity refreshToken) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void clearTokensForClient(ClientDetailsEntity client) { TypedQuery queryA = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_CLIENT, OAuth2AccessTokenEntity.class); queryA.setParameter(OAuth2AccessTokenEntity.PARAM_CLIENT, client); @@ -155,9 +169,6 @@ public void clearTokensForClient(ClientDetailsEntity client) { } } - /* (non-Javadoc) - * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getAccessTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity) - */ @Override public List getAccessTokensForClient(ClientDetailsEntity client) { TypedQuery queryA = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_CLIENT, OAuth2AccessTokenEntity.class); @@ -166,9 +177,6 @@ public List getAccessTokensForClient(ClientDetailsEntit return accessTokens; } - /* (non-Javadoc) - * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getRefreshTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity) - */ @Override public List getRefreshTokensForClient(ClientDetailsEntity client) { TypedQuery queryR = manager.createNamedQuery(OAuth2RefreshTokenEntity.QUERY_BY_CLIENT, OAuth2RefreshTokenEntity.class); @@ -176,37 +184,49 @@ public List getRefreshTokensForClient(ClientDetailsEnt List refreshTokens = queryR.getResultList(); return refreshTokens; } - - /* (non-Javadoc) - * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getAccessTokenForIdToken(org.mitre.oauth2.model.OAuth2AccessTokenEntity) - */ + @Override - public OAuth2AccessTokenEntity getAccessTokenForIdToken(OAuth2AccessTokenEntity idToken) { - TypedQuery queryA = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_ID_TOKEN, OAuth2AccessTokenEntity.class); - queryA.setParameter(OAuth2AccessTokenEntity.PARAM_ID_TOKEN, idToken); - List accessTokens = queryA.getResultList(); - return JpaUtil.getSingleResult(accessTokens); + public Set getAccessTokensByUserName(String name) { + TypedQuery query = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_NAME, OAuth2AccessTokenEntity.class); + query.setParameter(OAuth2AccessTokenEntity.PARAM_NAME, name); + List results = query.getResultList(); + return results != null ? new HashSet<>(results) : new HashSet<>(); + } + + @Override + public Set getRefreshTokensByUserName(String name) { + TypedQuery query = manager.createNamedQuery(OAuth2RefreshTokenEntity.QUERY_BY_NAME, OAuth2RefreshTokenEntity.class); + query.setParameter(OAuth2RefreshTokenEntity.PARAM_NAME, name); + List results = query.getResultList(); + return results != null ? new HashSet<>(results) : new HashSet<>(); } @Override public Set getAllExpiredAccessTokens() { + DefaultPageCriteria pageCriteria = new DefaultPageCriteria(0, MAXEXPIREDRESULTS); + return getAllExpiredAccessTokens(pageCriteria); + } + + @Override + public Set getAllExpiredAccessTokens(PageCriteria pageCriteria) { TypedQuery query = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_EXPIRED_BY_DATE, OAuth2AccessTokenEntity.class); query.setParameter(OAuth2AccessTokenEntity.PARAM_DATE, new Date()); - query.setMaxResults(MAXEXPIREDRESULTS); - return new LinkedHashSet<>(query.getResultList()); + return new LinkedHashSet<>(JpaUtil.getResultPage(query, pageCriteria)); } @Override public Set getAllExpiredRefreshTokens() { + DefaultPageCriteria pageCriteria = new DefaultPageCriteria(0, MAXEXPIREDRESULTS); + return getAllExpiredRefreshTokens(pageCriteria); + } + + @Override + public Set getAllExpiredRefreshTokens(PageCriteria pageCriteria) { TypedQuery query = manager.createNamedQuery(OAuth2RefreshTokenEntity.QUERY_EXPIRED_BY_DATE, OAuth2RefreshTokenEntity.class); - query.setParameter(OAuth2RefreshTokenEntity.PARAM_DATE, new Date()); - query.setMaxResults(MAXEXPIREDRESULTS); - return new LinkedHashSet<>(query.getResultList()); + query.setParameter(OAuth2AccessTokenEntity.PARAM_DATE, new Date()); + return new LinkedHashSet<>(JpaUtil.getResultPage(query,pageCriteria)); } - /* (non-Javadoc) - * @see org.mitre.oauth2.repository.OAuth2TokenRepository#getAccessTokensForResourceSet(org.mitre.uma.model.ResourceSet) - */ @Override public Set getAccessTokensForResourceSet(ResourceSet rs) { TypedQuery query = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_RESOURCE_SET, OAuth2AccessTokenEntity.class); @@ -214,4 +234,55 @@ public Set getAccessTokensForResourceSet(ResourceSet rs return new LinkedHashSet<>(query.getResultList()); } + @Override + @Transactional(value="defaultTransactionManager") + public void clearDuplicateAccessTokens() { + Query query = manager.createQuery("select a.jwt, count(1) as c from OAuth2AccessTokenEntity a GROUP BY a.jwt HAVING count(1) > 1"); + @SuppressWarnings("unchecked") + List resultList = query.getResultList(); + List values = new ArrayList<>(); + for (Object[] r : resultList) { + logger.warn("Found duplicate access tokens: {}, {}", ((JWT)r[0]).serialize(), r[1]); + values.add((JWT) r[0]); + } + if (values.size() > 0) { + CriteriaBuilder cb = manager.getCriteriaBuilder(); + CriteriaDelete criteriaDelete = cb.createCriteriaDelete(OAuth2AccessTokenEntity.class); + Root root = criteriaDelete.from(OAuth2AccessTokenEntity.class); + criteriaDelete.where(root.get("jwt").in(values)); + int result = manager.createQuery(criteriaDelete).executeUpdate(); + logger.warn("Deleted {} duplicate access tokens", result); + } + } + + @Override + @Transactional(value="defaultTransactionManager") + public void clearDuplicateRefreshTokens() { + Query query = manager.createQuery("select a.jwt, count(1) as c from OAuth2RefreshTokenEntity a GROUP BY a.jwt HAVING count(1) > 1"); + @SuppressWarnings("unchecked") + List resultList = query.getResultList(); + List values = new ArrayList<>(); + for (Object[] r : resultList) { + logger.warn("Found duplicate refresh tokens: {}, {}", ((JWT)r[0]).serialize(), r[1]); + values.add((JWT) r[0]); + } + if (values.size() > 0) { + CriteriaBuilder cb = manager.getCriteriaBuilder(); + CriteriaDelete criteriaDelete = cb.createCriteriaDelete(OAuth2RefreshTokenEntity.class); + Root root = criteriaDelete.from(OAuth2RefreshTokenEntity.class); + criteriaDelete.where(root.get("jwt").in(values)); + int result = manager.createQuery(criteriaDelete).executeUpdate(); + logger.warn("Deleted {} duplicate refresh tokens", result); + } + + } + + @Override + public List getAccessTokensForApprovedSite(ApprovedSite approvedSite) { + TypedQuery queryA = manager.createNamedQuery(OAuth2AccessTokenEntity.QUERY_BY_APPROVED_SITE, OAuth2AccessTokenEntity.class); + queryA.setParameter(OAuth2AccessTokenEntity.PARAM_APPROVED_SITE, approvedSite); + List accessTokens = queryA.getResultList(); + return accessTokens; + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java index 53027e8c11..f646b57243 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/repository/impl/JpaSystemScopeRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,13 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.repository.impl; +import static org.mitre.util.jpa.JpaUtil.getSingleResult; +import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; + import java.util.LinkedHashSet; import java.util.Set; @@ -31,9 +35,6 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import static org.mitre.util.jpa.JpaUtil.getSingleResult; -import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; - /** * @author jricher * @@ -41,14 +42,14 @@ @Repository("jpaSystemScopeRepository") public class JpaSystemScopeRepository implements SystemScopeRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager em; /* (non-Javadoc) * @see org.mitre.oauth2.repository.SystemScopeRepository#getAll() */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Set getAll() { TypedQuery query = em.createNamedQuery(SystemScope.QUERY_ALL, SystemScope.class); @@ -59,7 +60,7 @@ public Set getAll() { * @see org.mitre.oauth2.repository.SystemScopeRepository#getById(java.lang.Long) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public SystemScope getById(Long id) { return em.find(SystemScope.class, id); } @@ -68,7 +69,7 @@ public SystemScope getById(Long id) { * @see org.mitre.oauth2.repository.SystemScopeRepository#getByValue(java.lang.String) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public SystemScope getByValue(String value) { TypedQuery query = em.createNamedQuery(SystemScope.QUERY_BY_VALUE, SystemScope.class); query.setParameter(SystemScope.PARAM_VALUE, value); @@ -79,7 +80,7 @@ public SystemScope getByValue(String value) { * @see org.mitre.oauth2.repository.SystemScopeRepository#remove(org.mitre.oauth2.model.SystemScope) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(SystemScope scope) { SystemScope found = getById(scope.getId()); @@ -93,7 +94,7 @@ public void remove(SystemScope scope) { * @see org.mitre.oauth2.repository.SystemScopeRepository#save(org.mitre.oauth2.model.SystemScope) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public SystemScope save(SystemScope scope) { return saveOrUpdate(scope.getId(), em, scope); } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java index a4adf7195e..fc45ed20b9 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/BlacklistAwareRedirectResolver.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +14,11 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.service.impl; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.service.BlacklistedSiteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; @@ -30,10 +30,10 @@ import com.google.common.base.Strings; /** - * + * * A redirect resolver that knows how to check against the blacklisted URIs * for forbidden values. Can be configured to do strict string matching also. - * + * * @author jricher * */ @@ -42,9 +42,12 @@ public class BlacklistAwareRedirectResolver extends DefaultRedirectResolver { @Autowired private BlacklistedSiteService blacklistService; - - private boolean strictMatch = false; - + + @Autowired + private ConfigurationPropertiesBean config; + + private boolean strictMatch = true; + /* (non-Javadoc) * @see org.springframework.security.oauth2.provider.endpoint.RedirectResolver#resolveRedirect(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails) */ @@ -65,7 +68,7 @@ public String resolveRedirect(String requestedRedirect, ClientDetails client) th */ @Override protected boolean redirectMatches(String requestedRedirect, String redirectUri) { - + if (isStrictMatch()) { // we're doing a strict string match for all clients return Strings.nullToEmpty(requestedRedirect).equals(redirectUri); @@ -73,25 +76,30 @@ protected boolean redirectMatches(String requestedRedirect, String redirectUri) // otherwise do the prefix-match from the library return super.redirectMatches(requestedRedirect, redirectUri); } - + } /** * @return the strictMatch */ public boolean isStrictMatch() { - return strictMatch; + if (config.isHeartMode()) { + // HEART mode enforces strict matching + return true; + } else { + return strictMatch; + } } /** * Set this to true to require exact string matches for all redirect URIs. (Default is false) - * + * * @param strictMatch the strictMatch to set */ public void setStrictMatch(boolean strictMatch) { this.strictMatch = strictMatch; } - - + + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultDeviceCodeService.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultDeviceCodeService.java new file mode 100644 index 0000000000..29f4ec8d22 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultDeviceCodeService.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * 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.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.mitre.data.AbstractPageOperationTemplate; +import org.mitre.oauth2.model.AuthenticationHolderEntity; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.DeviceCode; +import org.mitre.oauth2.repository.impl.DeviceCodeRepository; +import org.mitre.oauth2.service.DeviceCodeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author jricher + * + */ +@Service("defaultDeviceCodeService") +public class DefaultDeviceCodeService implements DeviceCodeService { + + @Autowired + private DeviceCodeRepository repository; + + private RandomValueStringGenerator randomGenerator = new RandomValueStringGenerator(); + + /* (non-Javadoc) + * @see org.mitre.oauth2.service.DeviceCodeService#save(org.mitre.oauth2.model.DeviceCode) + */ + @Override + public DeviceCode createNewDeviceCode(Set requestedScopes, ClientDetailsEntity client, Map parameters) { + + // create a device code, should be big and random + String deviceCode = UUID.randomUUID().toString(); + + // create a user code, should be random but small and typable, and always uppercase (lookup is case insensitive) + String userCode = randomGenerator.generate().toUpperCase(); + + DeviceCode dc = new DeviceCode(deviceCode, userCode, requestedScopes, client.getClientId(), parameters); + + if (client.getDeviceCodeValiditySeconds() != null) { + dc.setExpiration(new Date(System.currentTimeMillis() + client.getDeviceCodeValiditySeconds() * 1000L)); + } + + dc.setApproved(false); + + return repository.save(dc); + } + + /* (non-Javadoc) + * @see org.mitre.oauth2.service.DeviceCodeService#lookUpByUserCode(java.lang.String) + */ + @Override + public DeviceCode lookUpByUserCode(String userCode) { + // always up-case the code for lookup + return repository.getByUserCode(userCode.toUpperCase()); + } + + /* (non-Javadoc) + * @see org.mitre.oauth2.service.DeviceCodeService#approveDeviceCode(org.mitre.oauth2.model.DeviceCode) + */ + @Override + public DeviceCode approveDeviceCode(DeviceCode dc, OAuth2Authentication auth) { + DeviceCode found = repository.getById(dc.getId()); + + found.setApproved(true); + + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); + authHolder.setAuthentication(auth); + + found.setAuthenticationHolder(authHolder); + + return repository.save(found); + } + + /* (non-Javadoc) + * @see org.mitre.oauth2.service.DeviceCodeService#consumeDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails) + */ + @Override + public DeviceCode findDeviceCode(String deviceCode, ClientDetails client) { + DeviceCode found = repository.getByDeviceCode(deviceCode); + + if (found != null) { + if (found.getClientId().equals(client.getClientId())) { + // make sure the client matches, if so, we're good + return found; + } else { + // if the clients don't match, pretend the code wasn't found + return null; + } + } else { + // didn't find the code, return null + return null; + } + + } + + + + /* (non-Javadoc) + * @see org.mitre.oauth2.service.DeviceCodeService#clearExpiredDeviceCodes() + */ + @Override + @Transactional(value="defaultTransactionManager") + public void clearExpiredDeviceCodes() { + + new AbstractPageOperationTemplate("clearExpiredDeviceCodes"){ + @Override + public Collection fetchPage() { + return repository.getExpiredCodes(); + } + + @Override + protected void doOperation(DeviceCode item) { + repository.remove(item); + } + }.execute(); + } + + /* (non-Javadoc) + * @see org.mitre.oauth2.service.DeviceCodeService#clearDeviceCode(java.lang.String, org.springframework.security.oauth2.provider.ClientDetails) + */ + @Override + public void clearDeviceCode(String deviceCode, ClientDetails client) { + DeviceCode found = findDeviceCode(deviceCode, client); + + if (found != null) { + // make sure it's not used twice + repository.remove(found); + } + + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java index aeca0f5b5b..ea36949fb6 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultIntrospectionResultAssembler.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +15,8 @@ *******************************************************************************/ package org.mitre.oauth2.service.impl; +import static com.google.common.collect.Maps.newLinkedHashMap; + import java.text.ParseException; import java.util.Map; import java.util.Set; @@ -33,8 +34,6 @@ import com.google.common.base.Joiner; import com.google.common.collect.Sets; -import static com.google.common.collect.Maps.newLinkedHashMap; - /** * Default implementation of the {@link IntrospectionResultAssembler} interface. */ @@ -57,7 +56,7 @@ public Map assembleFrom(OAuth2AccessTokenEntity accessToken, Use if (accessToken.getPermissions() != null && !accessToken.getPermissions().isEmpty()) { Set permissions = Sets.newHashSet(); - + for (Permission perm : accessToken.getPermissions()) { Map o = newLinkedHashMap(); o.put("resource_set_id", perm.getResourceSet().getId().toString()); @@ -65,14 +64,14 @@ public Map assembleFrom(OAuth2AccessTokenEntity accessToken, Use o.put("scopes", scopes); permissions.add(o); } - + result.put("permissions", permissions); - + } else { Set scopes = Sets.intersection(authScopes, accessToken.getScope()); - + result.put(SCOPE, Joiner.on(SCOPE_SEPARATOR).join(scopes)); - + } if (accessToken.getExpiration() != null) { @@ -92,7 +91,9 @@ public Map assembleFrom(OAuth2AccessTokenEntity accessToken, Use result.put(SUB, authentication.getName()); } - result.put(USER_ID, authentication.getName()); + if(authentication.getUserAuthentication() != null) { + result.put(USER_ID, authentication.getUserAuthentication().getName()); + } result.put(CLIENT_ID, authentication.getOAuth2Request().getClientId()); @@ -110,7 +111,7 @@ public Map assembleFrom(OAuth2RefreshTokenEntity refreshToken, U result.put(ACTIVE, true); Set scopes = Sets.intersection(authScopes, authentication.getOAuth2Request().getScope()); - + result.put(SCOPE, Joiner.on(SCOPE_SEPARATOR).join(scopes)); if (refreshToken.getExpiration() != null) { @@ -131,7 +132,9 @@ public Map assembleFrom(OAuth2RefreshTokenEntity refreshToken, U result.put(SUB, authentication.getName()); } - result.put(USER_ID, authentication.getName()); + if(authentication.getUserAuthentication() != null) { + result.put(USER_ID, authentication.getUserAuthentication().getName()); + } result.put(CLIENT_ID, authentication.getOAuth2Request().getClientId()); diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java index e296323114..b062a1a4ae 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2AuthorizationCodeService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,13 +16,14 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.service.impl; import java.util.Collection; import java.util.Date; +import org.mitre.data.AbstractPageOperationTemplate; import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.mitre.oauth2.model.AuthorizationCodeEntity; import org.mitre.oauth2.repository.AuthenticationHolderRepository; @@ -38,7 +40,7 @@ /** * Database-backed, random-value authorization code service implementation. - * + * * @author aanganes * */ @@ -49,23 +51,24 @@ public class DefaultOAuth2AuthorizationCodeService implements AuthorizationCodeS @Autowired private AuthorizationCodeRepository repository; - + @Autowired private AuthenticationHolderRepository authenticationHolderRepository; - + private int authCodeExpirationSeconds = 60 * 5; // expire in 5 minutes by default - private RandomValueStringGenerator generator = new RandomValueStringGenerator(); + private RandomValueStringGenerator generator = new RandomValueStringGenerator(22); /** * Generate a random authorization code and create an AuthorizationCodeEntity, * which will be stored in the repository. - * + * * @param authentication the authentication of the current user, to be retrieved when the * code is consumed * @return the authorization code */ @Override + @Transactional(value="defaultTransactionManager") public String createAuthorizationCode(OAuth2Authentication authentication) { String code = generator.generate(); @@ -75,8 +78,8 @@ public String createAuthorizationCode(OAuth2Authentication authentication) { authHolder = authenticationHolderRepository.save(authHolder); // set the auth code to expire - Date expiration = new Date(System.currentTimeMillis() + (getAuthCodeExpirationSeconds() * 1000L)); - + Date expiration = new Date(System.currentTimeMillis() + (getAuthCodeExpirationSeconds() * 1000L)); + AuthorizationCodeEntity entity = new AuthorizationCodeEntity(code, authHolder, expiration); repository.save(entity); @@ -88,7 +91,7 @@ public String createAuthorizationCode(OAuth2Authentication authentication) { * Match the provided string to an AuthorizationCodeEntity. If one is found, return * the authentication associated with the code. If one is not found, throw an * InvalidGrantException. - * + * * @param code the authorization code * @return the authentication that made the original request * @throws InvalidGrantException, if an AuthorizationCodeEntity is not found with the given value @@ -97,32 +100,35 @@ public String createAuthorizationCode(OAuth2Authentication authentication) { public OAuth2Authentication consumeAuthorizationCode(String code) throws InvalidGrantException { AuthorizationCodeEntity result = repository.getByCode(code); - + if (result == null) { throw new InvalidGrantException("JpaAuthorizationCodeRepository: no authorization code found for value " + code); } - + OAuth2Authentication auth = result.getAuthenticationHolder().getAuthentication(); - + repository.remove(result); - + return auth; } - + /** * Find and remove all expired auth codes. */ - @Transactional + @Transactional(value="defaultTransactionManager") public void clearExpiredAuthorizationCodes() { - - Collection codes = repository.getExpiredCodes(); - - for (AuthorizationCodeEntity code : codes) { - repository.remove(code); - } - - logger.info("Removed " + codes.size() + " expired authorization codes."); - + + new AbstractPageOperationTemplate("clearExpiredAuthorizationCodes"){ + @Override + public Collection fetchPage() { + return repository.getExpiredCodes(); + } + + @Override + protected void doOperation(AuthorizationCodeEntity item) { + repository.remove(item); + } + }.execute(); } /** diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java index d7f6f429cd..3416b84ea1 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ClientDetailsEntityService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -31,6 +32,7 @@ import org.apache.http.client.HttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.repository.OAuth2ClientRepository; import org.mitre.oauth2.repository.OAuth2TokenRepository; @@ -52,6 +54,8 @@ import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; import com.google.common.base.Strings; import com.google.common.cache.CacheBuilder; @@ -89,10 +93,10 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt @Autowired private StatsService statsService; - + @Autowired private ResourceSetService resourceSetService; - + @Autowired private ConfigurationPropertiesBean config; @@ -100,7 +104,7 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt private LoadingCache> sectorRedirects = CacheBuilder.newBuilder() .expireAfterAccess(1, TimeUnit.HOURS) .maximumSize(100) - .build(new SectorIdentifierLoader()); + .build(new SectorIdentifierLoader(HttpClientBuilder.create().useSystemProperties().build())); @Override public ClientDetailsEntity saveNewClient(ClientDetailsEntity client) { @@ -124,10 +128,13 @@ public ClientDetailsEntity saveNewClient(ClientDetailsEntity client) { // make sure that clients with the "refresh_token" grant type have the "offline_access" scope, and vice versa ensureRefreshTokenConsistency(client); - + // make sure we don't have both a JWKS and a JWKS URI ensureKeyConsistency(client); + // check consistency when using HEART mode + checkHeartMode(client); + // timestamp this to right now client.setCreatedAt(new Date()); @@ -146,6 +153,7 @@ public ClientDetailsEntity saveNewClient(ClientDetailsEntity client) { } /** + * Make sure the client has only one type of key registered * @param client */ private void ensureKeyConsistency(ClientDetailsEntity client) { @@ -155,15 +163,22 @@ private void ensureKeyConsistency(ClientDetailsEntity client) { } } + /** + * Make sure the client doesn't request any system reserved scopes + */ private void ensureNoReservedScopes(ClientDetailsEntity client) { // make sure a client doesn't get any special system scopes Set requestedScope = scopeService.fromStrings(client.getScope()); - + requestedScope = scopeService.removeReservedScopes(requestedScope); - + client.setScope(scopeService.toStrings(requestedScope)); } + /** + * Load the sector identifier URI if it exists and check the redirect URIs against it + * @param client + */ private void checkSectorIdentifierUri(ClientDetailsEntity client) { if (!Strings.isNullOrEmpty(client.getSectorIdentifierUri())) { try { @@ -183,6 +198,10 @@ private void checkSectorIdentifierUri(ClientDetailsEntity client) { } } + /** + * Make sure the client has the appropriate scope and grant type. + * @param client + */ private void ensureRefreshTokenConsistency(ClientDetailsEntity client) { if (client.getAuthorizedGrantTypes().contains("refresh_token") || client.getScope().contains(SystemScopeService.OFFLINE_ACCESS)) { @@ -191,6 +210,117 @@ private void ensureRefreshTokenConsistency(ClientDetailsEntity client) { } } + /** + * If HEART mode is enabled, make sure the client meets the requirements: + * - Only one of authorization_code, implicit, or client_credentials can be used at a time + * - A redirect_uri must be registered with either authorization_code or implicit + * - A key must be registered + * - A client secret must not be generated + * - authorization_code and client_credentials must use the private_key authorization method + * @param client + */ + private void checkHeartMode(ClientDetailsEntity client) { + if (config.isHeartMode()) { + if (client.getGrantTypes().contains("authorization_code")) { + // make sure we don't have incompatible grant types + if (client.getGrantTypes().contains("implicit") || client.getGrantTypes().contains("client_credentials")) { + throw new IllegalArgumentException("[HEART mode] Incompatible grant types"); + } + + // make sure we've got the right authentication method + if (client.getTokenEndpointAuthMethod() == null || !client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) { + throw new IllegalArgumentException("[HEART mode] Authorization code clients must use the private_key authentication method"); + } + + // make sure we've got a redirect URI + if (client.getRedirectUris().isEmpty()) { + throw new IllegalArgumentException("[HEART mode] Authorization code clients must register at least one redirect URI"); + } + } + + if (client.getGrantTypes().contains("implicit")) { + // make sure we don't have incompatible grant types + if (client.getGrantTypes().contains("authorization_code") || client.getGrantTypes().contains("client_credentials") || client.getGrantTypes().contains("refresh_token")) { + throw new IllegalArgumentException("[HEART mode] Incompatible grant types"); + } + + // make sure we've got the right authentication method + if (client.getTokenEndpointAuthMethod() == null || !client.getTokenEndpointAuthMethod().equals(AuthMethod.NONE)) { + throw new IllegalArgumentException("[HEART mode] Implicit clients must use the none authentication method"); + } + + // make sure we've got a redirect URI + if (client.getRedirectUris().isEmpty()) { + throw new IllegalArgumentException("[HEART mode] Implicit clients must register at least one redirect URI"); + } + } + + if (client.getGrantTypes().contains("client_credentials")) { + // make sure we don't have incompatible grant types + if (client.getGrantTypes().contains("authorization_code") || client.getGrantTypes().contains("implicit") || client.getGrantTypes().contains("refresh_token")) { + throw new IllegalArgumentException("[HEART mode] Incompatible grant types"); + } + + // make sure we've got the right authentication method + if (client.getTokenEndpointAuthMethod() == null || !client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) { + throw new IllegalArgumentException("[HEART mode] Client credentials clients must use the private_key authentication method"); + } + + // make sure we've got a redirect URI + if (!client.getRedirectUris().isEmpty()) { + throw new IllegalArgumentException("[HEART mode] Client credentials clients must not register a redirect URI"); + } + + } + + if (client.getGrantTypes().contains("password")) { + throw new IllegalArgumentException("[HEART mode] Password grant type is forbidden"); + } + + // make sure we don't have a client secret + if (!Strings.isNullOrEmpty(client.getClientSecret())) { + throw new IllegalArgumentException("[HEART mode] Client secrets are not allowed"); + } + + // make sure we've got a key registered + if (client.getJwks() == null && Strings.isNullOrEmpty(client.getJwksUri())) { + throw new IllegalArgumentException("[HEART mode] All clients must have a key registered"); + } + + // make sure our redirect URIs each fit one of the allowed categories + if (client.getRedirectUris() != null && !client.getRedirectUris().isEmpty()) { + boolean localhost = false; + boolean remoteHttps = false; + boolean customScheme = false; + for (String uri : client.getRedirectUris()) { + UriComponents components = UriComponentsBuilder.fromUriString(uri).build(); + if (components.getScheme() == null) { + // this is a very unknown redirect URI + customScheme = true; + } else if (components.getScheme().equals("http")) { + // http scheme, check for localhost + if (components.getHost().equals("localhost") || components.getHost().equals("127.0.0.1")) { + localhost = true; + } else { + throw new IllegalArgumentException("[HEART mode] Can't have an http redirect URI on non-local host"); + } + } else if (components.getScheme().equals("https")) { + remoteHttps = true; + } else { + customScheme = true; + } + } + + // now we make sure the client has a URI in only one of each of the three categories + if (!((localhost ^ remoteHttps ^ customScheme) + && !(localhost && remoteHttps && customScheme))) { + throw new IllegalArgumentException("[HEART mode] Can't have more than one class of redirect URI"); + } + } + + } + } + /** * Get the client by its internal ID */ @@ -240,7 +370,7 @@ public void deleteClient(ClientDetailsEntity client) throws InvalidClientExcepti if (whitelistedSite != null) { whitelistedSiteService.remove(whitelistedSite); } - + // clear out resource sets registered for this client Collection resourceSets = resourceSetService.getAllForClient(client); for (ResourceSet rs : resourceSets) { @@ -257,16 +387,16 @@ public void deleteClient(ClientDetailsEntity client) throws InvalidClientExcepti /** * Update the oldClient with information from the newClient. The * id from oldClient is retained. - * + * * Checks to make sure the refresh grant type and * the scopes are set appropriately. - * + * * Checks to make sure the redirect URIs aren't blacklisted. - * + * * Attempts to load the redirect URI (possibly cached) to check the * sector identifier against the contents there. - * - * + * + * */ @Override public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDetailsEntity newClient) throws IllegalArgumentException { @@ -284,6 +414,9 @@ public ClientDetailsEntity updateClient(ClientDetailsEntity oldClient, ClientDet // make sure we don't have both a JWKS and a JWKS URI ensureKeyConsistency(newClient); + // check consistency when using HEART mode + checkHeartMode(newClient); + // check the sector URI checkSectorIdentifierUri(newClient); @@ -317,22 +450,31 @@ public ClientDetailsEntity generateClientId(ClientDetailsEntity client) { */ @Override public ClientDetailsEntity generateClientSecret(ClientDetailsEntity client) { - client.setClientSecret(Base64.encodeBase64URLSafeString(new BigInteger(512, new SecureRandom()).toByteArray()).replace("=", "")); + if (config.isHeartMode()) { + logger.error("[HEART mode] Can't generate a client secret, skipping step; client won't be saved due to invalid configuration"); + client.setClientSecret(null); + } else { + client.setClientSecret(Base64.encodeBase64URLSafeString(new BigInteger(512, new SecureRandom()).toByteArray()).replace("=", "")); + } return client; } /** * Utility class to load a sector identifier's set of authorized redirect URIs. - * + * * @author jricher * */ private class SectorIdentifierLoader extends CacheLoader> { - private HttpClient httpClient = HttpClientBuilder.create().useSystemProperties().build(); - private HttpComponentsClientHttpRequestFactory httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - private RestTemplate restTemplate = new RestTemplate(httpFactory); + private HttpComponentsClientHttpRequestFactory httpFactory; + private RestTemplate restTemplate; private JsonParser parser = new JsonParser(); + SectorIdentifierLoader(HttpClient httpClient) { + this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + this.restTemplate = new RestTemplate(httpFactory); + } + @Override public List load(String key) throws Exception { diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java index d93420717c..641bf96faa 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultOAuth2ProviderTokenService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,17 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.service.impl; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_VERIFIER; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.Date; import java.util.HashSet; @@ -26,10 +34,13 @@ import java.util.Set; import java.util.UUID; +import org.mitre.data.AbstractPageOperationTemplate; +import org.mitre.data.DefaultPageCriteria; import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.oauth2.model.PKCEAlgorithm; import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.repository.AuthenticationHolderRepository; import org.mitre.oauth2.repository.OAuth2TokenRepository; @@ -44,6 +55,7 @@ import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.exceptions.InvalidRequestException; import org.springframework.security.oauth2.common.exceptions.InvalidScopeException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.OAuth2Authentication; @@ -51,15 +63,17 @@ import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -import com.google.common.collect.Sets; +import com.google.common.base.Strings; +import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.PlainJWT; /** * @author jricher - * + * */ @Service("defaultOAuth2ProviderTokenService") public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityService { @@ -84,60 +98,103 @@ public class DefaultOAuth2ProviderTokenService implements OAuth2TokenEntityServi @Autowired private SystemScopeService scopeService; - @Override - public Set getAllAccessTokensForUser(String id) { - - Set all = tokenRepository.getAllAccessTokens(); - Set results = Sets.newLinkedHashSet(); - - for (OAuth2AccessTokenEntity token : all) { - if (token.getAuthenticationHolder().getAuthentication().getName().equals(id)) { - results.add(token); - } - } + @Autowired + private ApprovedSiteService approvedSiteService; - return results; + @Override + public Set getAllAccessTokensForUser(String userName) { + return tokenRepository.getAccessTokensByUserName(userName); } - @Override - public Set getAllRefreshTokensForUser(String id) { - Set all = tokenRepository.getAllRefreshTokens(); - Set results = Sets.newLinkedHashSet(); - - for (OAuth2RefreshTokenEntity token : all) { - if (token.getAuthenticationHolder().getAuthentication().getName().equals(id)) { - results.add(token); - } - } - - return results; + public Set getAllRefreshTokensForUser(String userName) { + return tokenRepository.getRefreshTokensByUserName(userName); } @Override public OAuth2AccessTokenEntity getAccessTokenById(Long id) { - return tokenRepository.getAccessTokenById(id); + return clearExpiredAccessToken(tokenRepository.getAccessTokenById(id)); } @Override public OAuth2RefreshTokenEntity getRefreshTokenById(Long id) { - return tokenRepository.getRefreshTokenById(id); + return clearExpiredRefreshToken(tokenRepository.getRefreshTokenById(id)); } - @Autowired - private ApprovedSiteService approvedSiteService; + /** + * Utility function to delete an access token that's expired before returning it. + * @param token the token to check + * @return null if the token is null or expired, the input token (unchanged) if it hasn't + */ + private OAuth2AccessTokenEntity clearExpiredAccessToken(OAuth2AccessTokenEntity token) { + if (token == null) { + return null; + } else if (token.isExpired()) { + // immediately revoke expired token + logger.debug("Clearing expired access token: " + token.getValue()); + revokeAccessToken(token); + return null; + } else { + return token; + } + } + /** + * Utility function to delete a refresh token that's expired before returning it. + * @param token the token to check + * @return null if the token is null or expired, the input token (unchanged) if it hasn't + */ + private OAuth2RefreshTokenEntity clearExpiredRefreshToken(OAuth2RefreshTokenEntity token) { + if (token == null) { + return null; + } else if (token.isExpired()) { + // immediately revoke expired token + logger.debug("Clearing expired refresh token: " + token.getValue()); + revokeRefreshToken(token); + return null; + } else { + return token; + } + } @Override + @Transactional(value="defaultTransactionManager") public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) throws AuthenticationException, InvalidClientException { if (authentication != null && authentication.getOAuth2Request() != null) { // look up our client - OAuth2Request clientAuth = authentication.getOAuth2Request(); + OAuth2Request request = authentication.getOAuth2Request(); - ClientDetailsEntity client = clientDetailsService.loadClientByClientId(clientAuth.getClientId()); + ClientDetailsEntity client = clientDetailsService.loadClientByClientId(request.getClientId()); if (client == null) { - throw new InvalidClientException("Client not found: " + clientAuth.getClientId()); + throw new InvalidClientException("Client not found: " + request.getClientId()); + } + + // handle the PKCE code challenge if present + if (request.getExtensions().containsKey(CODE_CHALLENGE)) { + String challenge = (String) request.getExtensions().get(CODE_CHALLENGE); + PKCEAlgorithm alg = PKCEAlgorithm.parse((String) request.getExtensions().get(CODE_CHALLENGE_METHOD)); + + String verifier = request.getRequestParameters().get(CODE_VERIFIER); + + if (alg.equals(PKCEAlgorithm.plain)) { + // do a direct string comparison + if (!challenge.equals(verifier)) { + throw new InvalidRequestException("Code challenge and verifier do not match"); + } + } else if (alg.equals(PKCEAlgorithm.S256)) { + // hash the verifier + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + String hash = Base64URL.encode(digest.digest(verifier.getBytes(StandardCharsets.US_ASCII))).toString(); + if (!challenge.equals(hash)) { + throw new InvalidRequestException("Code challenge and verifier do not match"); + } + } catch (NoSuchAlgorithmException e) { + logger.error("Unknown algorithm for PKCE digest", e); + } + } + } OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity();//accessTokenFactory.createNewAccessToken(); @@ -148,11 +205,11 @@ public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentica // inherit the scope from the auth, but make a new set so it is //not unmodifiable. Unmodifiables don't play nicely with Eclipselink, which //wants to use the clone operation. - Set scopes = scopeService.fromStrings(clientAuth.getScope()); + Set scopes = scopeService.fromStrings(request.getScope()); // remove any of the special system scopes - scopes = scopeService.removeReservedScopes(scopes); - + scopes = scopeService.removeReservedScopes(scopes); + token.setScope(scopeService.toStrings(scopes)); // make it expire if necessary @@ -175,10 +232,6 @@ public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentica token.setRefreshToken(savedRefreshToken); } - OAuth2AccessTokenEntity enhancedToken = (OAuth2AccessTokenEntity) tokenEnhancer.enhance(token, authentication); - - OAuth2AccessTokenEntity savedToken = tokenRepository.saveAccessToken(enhancedToken); - //Add approved site reference, if any OAuth2Request originalAuthRequest = authHolder.getAuthentication().getOAuth2Request(); @@ -186,13 +239,14 @@ public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentica Long apId = Long.parseLong((String) originalAuthRequest.getExtensions().get("approved_site")); ApprovedSite ap = approvedSiteService.getById(apId); - Set apTokens = ap.getApprovedAccessTokens(); - apTokens.add(savedToken); - ap.setApprovedAccessTokens(apTokens); - approvedSiteService.save(ap); + token.setApprovedSite(ap); } + OAuth2AccessTokenEntity enhancedToken = (OAuth2AccessTokenEntity) tokenEnhancer.enhance(token, authentication); + + OAuth2AccessTokenEntity savedToken = saveAccessToken(enhancedToken); + if (savedToken.getRefreshToken() != null) { tokenRepository.saveRefreshToken(savedToken.getRefreshToken()); // make sure we save any changes that might have been enhanced } @@ -206,41 +260,46 @@ public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentica private OAuth2RefreshTokenEntity createRefreshToken(ClientDetailsEntity client, AuthenticationHolderEntity authHolder) { OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity(); //refreshTokenFactory.createNewRefreshToken(); - JWTClaimsSet refreshClaims = new JWTClaimsSet(); + JWTClaimsSet.Builder refreshClaims = new JWTClaimsSet.Builder(); // make it expire if necessary if (client.getRefreshTokenValiditySeconds() != null) { Date expiration = new Date(System.currentTimeMillis() + (client.getRefreshTokenValiditySeconds() * 1000L)); refreshToken.setExpiration(expiration); - refreshClaims.setExpirationTime(expiration); + refreshClaims.expirationTime(expiration); } // set a random identifier - refreshClaims.setJWTID(UUID.randomUUID().toString()); + refreshClaims.jwtID(UUID.randomUUID().toString()); // TODO: add issuer fields, signature to JWT - PlainJWT refreshJwt = new PlainJWT(refreshClaims); + PlainJWT refreshJwt = new PlainJWT(refreshClaims.build()); refreshToken.setJwt(refreshJwt); //Add the authentication refreshToken.setAuthenticationHolder(authHolder); refreshToken.setClient(client); - - // save the token first so that we can set it to a member of the access token (NOTE: is this step necessary?) OAuth2RefreshTokenEntity savedRefreshToken = tokenRepository.saveRefreshToken(refreshToken); return savedRefreshToken; } @Override + @Transactional(value="defaultTransactionManager") public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, TokenRequest authRequest) throws AuthenticationException { + + if (Strings.isNullOrEmpty(refreshTokenValue)) { + // throw an invalid token exception if there's no refresh token value at all + throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue); + } - OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenByValue(refreshTokenValue); + OAuth2RefreshTokenEntity refreshToken = clearExpiredRefreshToken(tokenRepository.getRefreshTokenByValue(refreshTokenValue)); if (refreshToken == null) { + // throw an invalid token exception if we couldn't find the token throw new InvalidTokenException("Invalid refresh token: " + refreshTokenValue); } @@ -280,7 +339,7 @@ public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, Toke Set scopeRequested = authRequest.getScope() == null ? new HashSet() : new HashSet<>(authRequest.getScope()); Set scope = scopeService.fromStrings(scopeRequested); - + // remove any of the special system scopes scope = scopeService.removeReservedScopes(scope); @@ -313,7 +372,7 @@ public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, Toke // otherwise, make a new refresh token OAuth2RefreshTokenEntity newRefresh = createRefreshToken(client, authHolder); token.setRefreshToken(newRefresh); - + // clean up the old refresh token tokenRepository.removeRefreshToken(refreshToken); } @@ -325,25 +384,17 @@ public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue, Toke tokenRepository.saveAccessToken(token); return token; - } @Override public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException { - - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue); + OAuth2AccessTokenEntity accessToken = clearExpiredAccessToken(tokenRepository.getAccessTokenByValue(accessTokenValue)); if (accessToken == null) { throw new InvalidTokenException("Invalid access token: " + accessTokenValue); + } else { + return accessToken.getAuthenticationHolder().getAuthentication(); } - - if (accessToken.isExpired()) { - //tokenRepository.removeAccessToken(accessToken); - revokeAccessToken(accessToken); - throw new InvalidTokenException("Expired access token: " + accessTokenValue); - } - - return accessToken.getAuthenticationHolder().getAuthentication(); } @@ -352,11 +403,10 @@ public OAuth2Authentication loadAuthentication(String accessTokenValue) throws A */ @Override public OAuth2AccessTokenEntity readAccessToken(String accessTokenValue) throws AuthenticationException { - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenByValue(accessTokenValue); + OAuth2AccessTokenEntity accessToken = clearExpiredAccessToken(tokenRepository.getAccessTokenByValue(accessTokenValue)); if (accessToken == null) { throw new InvalidTokenException("Access token for value " + accessTokenValue + " was not found"); - } - else { + } else { return accessToken; } } @@ -388,6 +438,7 @@ public OAuth2RefreshTokenEntity getRefreshToken(String refreshTokenValue) throws * Revoke a refresh token and all access tokens issued to it. */ @Override + @Transactional(value="defaultTransactionManager") public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) { tokenRepository.clearAccessTokensForRefreshToken(refreshToken); tokenRepository.removeRefreshToken(refreshToken); @@ -397,22 +448,16 @@ public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) { * Revoke an access token. */ @Override + @Transactional(value="defaultTransactionManager") public void revokeAccessToken(OAuth2AccessTokenEntity accessToken) { tokenRepository.removeAccessToken(accessToken); } - - /* (non-Javadoc) - * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getAccessTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity) - */ @Override public List getAccessTokensForClient(ClientDetailsEntity client) { return tokenRepository.getAccessTokensForClient(client); } - /* (non-Javadoc) - * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getRefreshTokensForClient(org.mitre.oauth2.model.ClientDetailsEntity) - */ @Override public List getRefreshTokensForClient(ClientDetailsEntity client) { return tokenRepository.getRefreshTokensForClient(client); @@ -423,57 +468,66 @@ public List getRefreshTokensForClient(ClientDetailsEnt */ @Override public void clearExpiredTokens() { - logger.info("Cleaning out all expired tokens"); - - Collection accessTokens = getExpiredAccessTokens(); - logger.info("Found " + accessTokens.size() + " expired access tokens"); - for (OAuth2AccessTokenEntity oAuth2AccessTokenEntity : accessTokens) { - try { - revokeAccessToken(oAuth2AccessTokenEntity); - } catch (IllegalArgumentException e) { - //An ID token is deleted with its corresponding access token, but then the ID token is on the list of expired tokens as well and there is - //nothing in place to distinguish it from any other. - //An attempt to delete an already deleted token returns an error, stopping the cleanup dead. We need it to keep going. + logger.debug("Cleaning out all expired tokens"); + + new AbstractPageOperationTemplate("clearExpiredAccessTokens") { + @Override + public Collection fetchPage() { + return tokenRepository.getAllExpiredAccessTokens(new DefaultPageCriteria()); } - } - Collection refreshTokens = getExpiredRefreshTokens(); - logger.info("Found " + refreshTokens.size() + " expired refresh tokens"); - for (OAuth2RefreshTokenEntity oAuth2RefreshTokenEntity : refreshTokens) { - revokeRefreshToken(oAuth2RefreshTokenEntity); - } + @Override + public void doOperation(OAuth2AccessTokenEntity item) { + revokeAccessToken(item); + } + }.execute(); - Collection authHolders = getOrphanedAuthenticationHolders(); - logger.info("Found " + authHolders.size() + " orphaned authentication holders"); - for(AuthenticationHolderEntity authHolder : authHolders) { - authenticationHolderRepository.remove(authHolder); - } - } + new AbstractPageOperationTemplate("clearExpiredRefreshTokens") { + @Override + public Collection fetchPage() { + return tokenRepository.getAllExpiredRefreshTokens(new DefaultPageCriteria()); + } - private Collection getExpiredAccessTokens() { - return Sets.newHashSet(tokenRepository.getAllExpiredAccessTokens()); - } + @Override + public void doOperation(OAuth2RefreshTokenEntity item) { + revokeRefreshToken(item); + } + }.execute(); - private Collection getExpiredRefreshTokens() { - return Sets.newHashSet(tokenRepository.getAllExpiredRefreshTokens()); - } + new AbstractPageOperationTemplate("clearExpiredAuthenticationHolders") { + @Override + public Collection fetchPage() { + return authenticationHolderRepository.getOrphanedAuthenticationHolders(new DefaultPageCriteria()); + } - private Collection getOrphanedAuthenticationHolders() { - return Sets.newHashSet(authenticationHolderRepository.getOrphanedAuthenticationHolders()); + @Override + public void doOperation(AuthenticationHolderEntity item) { + authenticationHolderRepository.remove(item); + } + }.execute(); } /* (non-Javadoc) * @see org.mitre.oauth2.service.OAuth2TokenEntityService#saveAccessToken(org.mitre.oauth2.model.OAuth2AccessTokenEntity) */ @Override + @Transactional(value="defaultTransactionManager") public OAuth2AccessTokenEntity saveAccessToken(OAuth2AccessTokenEntity accessToken) { - return tokenRepository.saveAccessToken(accessToken); + OAuth2AccessTokenEntity newToken = tokenRepository.saveAccessToken(accessToken); + + // if the old token has any additional information for the return from the token endpoint, carry it through here after save + if (accessToken.getAdditionalInformation() != null && !accessToken.getAdditionalInformation().isEmpty()) { + newToken.getAdditionalInformation().putAll(accessToken.getAdditionalInformation()); + } + + return newToken; } /* (non-Javadoc) * @see org.mitre.oauth2.service.OAuth2TokenEntityService#saveRefreshToken(org.mitre.oauth2.model.OAuth2RefreshTokenEntity) */ @Override + @Transactional(value="defaultTransactionManager") public OAuth2RefreshTokenEntity saveRefreshToken(OAuth2RefreshTokenEntity refreshToken) { return tokenRepository.saveRefreshToken(refreshToken); } @@ -492,15 +546,6 @@ public void setTokenEnhancer(TokenEnhancer tokenEnhancer) { this.tokenEnhancer = tokenEnhancer; } - /* (non-Javadoc) - * @see org.mitre.oauth2.service.OAuth2TokenEntityService#getAccessTokenForIdToken(org.mitre.oauth2.model.OAuth2AccessTokenEntity) - */ - @Override - public OAuth2AccessTokenEntity getAccessTokenForIdToken(OAuth2AccessTokenEntity idToken) { - return tokenRepository.getAccessTokenForIdToken(idToken); - } - - @Override public OAuth2AccessTokenEntity getRegistrationAccessTokenForClient(ClientDetailsEntity client) { List allTokens = getAccessTokensForClient(client); @@ -515,7 +560,4 @@ public OAuth2AccessTokenEntity getRegistrationAccessTokenForClient(ClientDetails return null; } - - - } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java index 687a7fe33e..21474fe6e0 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/service/impl/DefaultSystemScopeService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,12 +16,11 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.service.impl; import java.util.LinkedHashSet; -import java.util.List; import java.util.Set; import org.mitre.oauth2.model.SystemScope; @@ -30,13 +30,10 @@ import org.springframework.stereotype.Service; import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; -import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.Collections2; -import com.google.common.collect.Lists; import com.google.common.collect.Sets; /** @@ -62,7 +59,7 @@ public boolean apply(SystemScope input) { return (input != null && input.isRestricted()); } }; - + private Predicate isReserved = new Predicate() { @Override public boolean apply(SystemScope input) { @@ -76,20 +73,11 @@ public SystemScope apply(String input) { if (Strings.isNullOrEmpty(input)) { return null; } else { - List parts = parseStructuredScopeValue(input); - String base = parts.get(0); // the first part is the base // get the real scope if it's available - SystemScope s = getByValue(base); + SystemScope s = getByValue(input); if (s == null) { // make a fake one otherwise - s = new SystemScope(base); - if (parts.size() > 1) { - s.setStructured(true); - } - } - - if (s.isStructured() && parts.size() > 1) { - s.setStructuredValue(parts.get(1)); + s = new SystemScope(input); } return s; @@ -103,11 +91,7 @@ public String apply(SystemScope input) { if (input == null) { return null; } else { - if (input.isStructured() && !Strings.isNullOrEmpty(input.getStructuredValue())) { - return Joiner.on(":").join(input.getValue(), input.getStructuredValue()); - } else { - return input.getValue(); - } + return input.getValue(); } } }; @@ -181,11 +165,6 @@ public Set toStrings(Set scope) { } } - // parse a structured scope string into its components - private List parseStructuredScopeValue(String value) { - return Lists.newArrayList(Splitter.on(":").split(value)); - } - /* (non-Javadoc) * @see org.mitre.oauth2.service.SystemScopeService#scopesMatch(java.util.Set, java.util.Set) */ @@ -198,22 +177,7 @@ public boolean scopesMatch(Set expected, Set actual) { for (SystemScope actScope : act) { // first check to see if there's an exact match if (!ex.contains(actScope)) { - // we didn't find an exact match - if (actScope.isStructured() && !Strings.isNullOrEmpty(actScope.getStructuredValue())) { - // if we didn't get an exact match but the actual scope is structured, we need to check further - - // first, find the "base" scope for this - SystemScope base = getByValue(actScope.getValue()); - if (!ex.contains(base)) { - // if the expected doesn't contain the base scope, fail - return false; - } else { - // we did find an exact match, need to check the rest - } - } else { - // the scope wasn't structured, fail now - return false; - } + return false; } else { // if we did find an exact match, we need to check the rest } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java b/openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java index 004c77a023..c53596f26b 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/token/ChainedTokenGranter.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.token; @@ -45,7 +46,7 @@ @Component("chainedTokenGranter") public class ChainedTokenGranter extends AbstractTokenGranter { - private static final String grantType = "urn:ietf:params:oauth:grant_type:redelegate"; + public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant_type:redelegate"; // keep down-cast versions so we can get to the right queries private OAuth2TokenEntityService tokenServices; @@ -53,11 +54,11 @@ public class ChainedTokenGranter extends AbstractTokenGranter { /** * @param tokenServices * @param clientDetailsService - * @param grantType + * @param GRANT_TYPE */ @Autowired public ChainedTokenGranter(OAuth2TokenEntityService tokenServices, ClientDetailsEntityService clientDetailsService, OAuth2RequestFactory requestFactory) { - super(tokenServices, clientDetailsService, requestFactory, grantType); + super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); this.tokenServices = tokenServices; } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/DeviceTokenGranter.java b/openid-connect-server/src/main/java/org/mitre/oauth2/token/DeviceTokenGranter.java new file mode 100644 index 0000000000..f7e185e530 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/token/DeviceTokenGranter.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * 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.token; + +import java.util.Date; + +import org.mitre.oauth2.exception.AuthorizationPendingException; +import org.mitre.oauth2.exception.DeviceCodeExpiredException; +import org.mitre.oauth2.model.DeviceCode; +import org.mitre.oauth2.service.DeviceCodeService; +import org.mitre.oauth2.web.DeviceEndpoint; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.ClientDetailsService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.security.oauth2.provider.TokenRequest; +import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; +import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; +import org.springframework.stereotype.Component; + +/** + * Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow + * + * @see DeviceEndpoint + * + * @author jricher + * + */ +@Component("deviceTokenGranter") +public class DeviceTokenGranter extends AbstractTokenGranter { + + public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"; + + @Autowired + private DeviceCodeService deviceCodeService; + + /** + * @param tokenServices + * @param clientDetailsService + * @param requestFactory + * @param grantType + */ + protected DeviceTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) { + super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); + } + + /* (non-Javadoc) + * @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest) + */ + @Override + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { + + String deviceCode = tokenRequest.getRequestParameters().get("device_code"); + + // look up the device code and consume it + DeviceCode dc = deviceCodeService.findDeviceCode(deviceCode, client); + + if (dc != null) { + + // make sure the code hasn't expired yet + if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) { + + deviceCodeService.clearDeviceCode(deviceCode, client); + + throw new DeviceCodeExpiredException("Device code has expired " + deviceCode); + + } else if (!dc.isApproved()) { + + // still waiting for approval + throw new AuthorizationPendingException("Authorization pending for code " + deviceCode); + + } else { + // inherit the (approved) scopes from the original request + tokenRequest.setScope(dc.getScope()); + + OAuth2Authentication auth = new OAuth2Authentication(getRequestFactory().createOAuth2Request(client, tokenRequest), dc.getAuthenticationHolder().getUserAuth()); + + deviceCodeService.clearDeviceCode(deviceCode, client); + + return auth; + } + } else { + throw new InvalidGrantException("Invalid device code: " + deviceCode); + } + + } + + + + +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java b/openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java index cb7c749494..02217fc48e 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/token/JWTAssertionTokenGranter.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,37 +16,30 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.token; import java.text.ParseException; -import java.util.Date; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.jwt.assertion.AssertionValidator; +import org.mitre.oauth2.assertion.AssertionOAuth2RequestFactory; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.OAuth2TokenEntityService; -import org.mitre.oauth2.service.SystemScopeService; -import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.assertion.JWTBearerAssertionAuthenticationToken; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.stereotype.Component; -import com.nimbusds.jose.JWSHeader; import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; -import com.nimbusds.jwt.SignedJWT; /** * @author jricher @@ -56,147 +50,47 @@ public class JWTAssertionTokenGranter extends AbstractTokenGranter { private static final String grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"; - // keep down-cast versions so we can get to the right queries - private OAuth2TokenEntityService tokenServices; - @Autowired - private JWTSigningAndValidationService jwtService; + @Qualifier("jwtAssertionValidator") + private AssertionValidator validator; @Autowired - private ConfigurationPropertiesBean config; + private AssertionOAuth2RequestFactory assertionFactory; @Autowired public JWTAssertionTokenGranter(OAuth2TokenEntityService tokenServices, ClientDetailsEntityService clientDetailsService, OAuth2RequestFactory requestFactory) { super(tokenServices, clientDetailsService, requestFactory, grantType); - this.tokenServices = tokenServices; } /* (non-Javadoc) * @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.AuthorizationRequest) */ @Override - protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException { + protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) throws AuthenticationException, InvalidTokenException { // read and load up the existing token - String incomingTokenValue = tokenRequest.getRequestParameters().get("assertion"); - OAuth2AccessTokenEntity incomingToken = tokenServices.readAccessToken(incomingTokenValue); - - if (incomingToken.getScope().contains(SystemScopeService.ID_TOKEN_SCOPE)) { - - if (!client.getClientId().equals(tokenRequest.getClientId())) { - throw new InvalidClientException("Not the right client for this token"); - } - - // it's an ID token, process it accordingly - - try { - - // TODO: make this use a more specific idtoken class - JWT idToken = JWTParser.parse(incomingTokenValue); - - OAuth2AccessTokenEntity accessToken = tokenServices.getAccessTokenForIdToken(incomingToken); - - if (accessToken != null) { - - //OAuth2AccessTokenEntity newIdToken = tokenServices.get - - OAuth2AccessTokenEntity newIdTokenEntity = new OAuth2AccessTokenEntity(); + try { + String incomingAssertionValue = tokenRequest.getRequestParameters().get("assertion"); + JWT assertion = JWTParser.parse(incomingAssertionValue); - // copy over all existing claims - JWTClaimsSet claims = new JWTClaimsSet(idToken.getJWTClaimsSet()); + if (validator.isValid(assertion)) { - if (client instanceof ClientDetailsEntity) { + // our validator says it's OK, time to make a token from it + // the real work happens in the assertion factory and the token services + return new OAuth2Authentication(assertionFactory.createOAuth2Request(client, tokenRequest, assertion), + new JWTBearerAssertionAuthenticationToken(assertion, client.getAuthorities())); - ClientDetailsEntity clientEntity = (ClientDetailsEntity) client; - - // update expiration and issued-at claims - if (clientEntity.getIdTokenValiditySeconds() != null) { - Date expiration = new Date(System.currentTimeMillis() + (clientEntity.getIdTokenValiditySeconds() * 1000L)); - claims.setExpirationTime(expiration); - newIdTokenEntity.setExpiration(expiration); - } - - } else { - //This should never happen - logger.fatal("SEVERE: Client is not an instance of OAuth2AccessTokenEntity."); - throw new BadCredentialsException("SEVERE: Client is not an instance of ClientDetailsEntity; JwtAssertionTokenGranter cannot process this request."); - } - - claims.setIssueTime(new Date()); - - - SignedJWT newIdToken = new SignedJWT((JWSHeader) idToken.getHeader(), claims); - jwtService.signJwt(newIdToken); - - newIdTokenEntity.setJwt(newIdToken); - newIdTokenEntity.setAuthenticationHolder(incomingToken.getAuthenticationHolder()); - newIdTokenEntity.setScope(incomingToken.getScope()); - newIdTokenEntity.setClient(incomingToken.getClient()); - - newIdTokenEntity = tokenServices.saveAccessToken(newIdTokenEntity); - - // attach the ID token to the access token entity - accessToken.setIdToken(newIdTokenEntity); - accessToken = tokenServices.saveAccessToken(accessToken); - - // delete the old ID token - tokenServices.revokeAccessToken(incomingToken); - - return newIdTokenEntity; - - } - } catch (ParseException e) { - logger.warn("Couldn't parse id token", e); + } else { + logger.warn("Incoming assertion did not pass validator, rejecting"); + return null; } + } catch (ParseException e) { + logger.warn("Unable to parse incoming assertion"); } - // if we got down here, we didn't actually create any tokens, so return null - + // if we had made a token, we'd have returned it by now, so return null here to close out with no created token return null; - /* - * Otherwise, process it like an access token assertion ... which we don't support yet so this is all commented out - * / - if (jwtService.validateSignature(incomingTokenValue)) { - - Jwt jwt = Jwt.parse(incomingTokenValue); - - - if (oldToken.getScope().contains("id-token")) { - // TODO: things - } - - // TODO: should any of these throw an exception instead of returning null? - JwtClaims claims = jwt.getClaims(); - if (!config.getIssuer().equals(claims.getIssuer())) { - // issuer isn't us - return null; - } - - if (!authorizationRequest.getClientId().equals(claims.getAudience())) { - // audience isn't the client - return null; - } - - Date now = new Date(); - if (!now.after(claims.getExpiration())) { - // token is expired - return null; - } - - // FIXME - // This doesn't work. We need to look up the old token, figure out its scopes and bind it appropriately. - // In the case of an ID token, we need to look up its parent access token and change the reference, and revoke the old one, and - // that's tricky. - // we might need new calls on the token services layer to handle this, and we might - // need to handle id tokens separately. - return new OAuth2Authentication(authorizationRequest, null); - - } else { - return null; // throw error?? - } - */ - } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/token/StructuredScopeAwareOAuth2RequestValidator.java b/openid-connect-server/src/main/java/org/mitre/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java similarity index 89% rename from openid-connect-server/src/main/java/org/mitre/oauth2/token/StructuredScopeAwareOAuth2RequestValidator.java rename to openid-connect-server/src/main/java/org/mitre/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java index a66e845753..3896dfd3a5 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/token/StructuredScopeAwareOAuth2RequestValidator.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/token/ScopeServiceAwareOAuth2RequestValidator.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.token; @@ -30,14 +31,14 @@ import org.springframework.security.oauth2.provider.TokenRequest; /** - * + * * Validates the scopes on a request by comparing them against a client's - * allowed scopes, but allow structured scopes to function. - * + * allowed scopes, but allow custom scopes to function through the system scopes + * * @author jricher - * + * */ -public class StructuredScopeAwareOAuth2RequestValidator implements OAuth2RequestValidator { +public class ScopeServiceAwareOAuth2RequestValidator implements OAuth2RequestValidator { @Autowired private SystemScopeService scopeService; diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java b/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java index 98e6ccfcaa..cd6eed06cc 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/view/TokenApiView.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -56,72 +55,71 @@ public class TokenApiView extends AbstractView { private static final Logger logger = LoggerFactory.getLogger(TokenApiView.class); private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { + .setExclusionStrategies(new ExclusionStrategy() { - @Override - public boolean shouldSkipField(FieldAttributes f) { - return false; - } + @Override + public boolean shouldSkipField(FieldAttributes f) { + return false; + } - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; - } - return false; - } + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } - }) - .registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer() { + }) + .registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer() { - @Override - public JsonElement serialize(OAuth2AccessTokenEntity src, - Type typeOfSrc, JsonSerializationContext context) { + @Override + public JsonElement serialize(OAuth2AccessTokenEntity src, + Type typeOfSrc, JsonSerializationContext context) { - JsonObject o = new JsonObject(); + JsonObject o = new JsonObject(); - o.addProperty("value", src.getValue()); - o.addProperty("id", src.getId()); - o.addProperty("idTokenId", src.getIdToken() != null ? src.getIdToken().getId() : null); - o.addProperty("refreshTokenId", src.getRefreshToken() != null ? src.getRefreshToken().getId() : null); + o.addProperty("value", src.getValue()); + o.addProperty("id", src.getId()); + o.addProperty("refreshTokenId", src.getRefreshToken() != null ? src.getRefreshToken().getId() : null); - o.add("scopes", context.serialize(src.getScope())); + o.add("scopes", context.serialize(src.getScope())); - o.addProperty("clientId", src.getClient().getClientId()); - o.addProperty("userId", src.getAuthenticationHolder().getAuthentication().getName()); + o.addProperty("clientId", src.getClient().getClientId()); + o.addProperty("userId", src.getAuthenticationHolder().getAuthentication().getName()); - o.add("expiration", context.serialize(src.getExpiration())); + o.add("expiration", context.serialize(src.getExpiration())); - return o; - } + return o; + } - }) - .registerTypeAdapter(OAuth2RefreshTokenEntity.class, new JsonSerializer() { + }) + .registerTypeAdapter(OAuth2RefreshTokenEntity.class, new JsonSerializer() { - @Override - public JsonElement serialize(OAuth2RefreshTokenEntity src, - Type typeOfSrc, JsonSerializationContext context) { - JsonObject o = new JsonObject(); + @Override + public JsonElement serialize(OAuth2RefreshTokenEntity src, + Type typeOfSrc, JsonSerializationContext context) { + JsonObject o = new JsonObject(); - o.addProperty("value", src.getValue()); - o.addProperty("id", src.getId()); + o.addProperty("value", src.getValue()); + o.addProperty("id", src.getId()); - o.add("scopes", context.serialize(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope())); + o.add("scopes", context.serialize(src.getAuthenticationHolder().getAuthentication().getOAuth2Request().getScope())); - o.addProperty("clientId", src.getClient().getClientId()); - o.addProperty("userId", src.getAuthenticationHolder().getAuthentication().getName()); + o.addProperty("clientId", src.getClient().getClientId()); + o.addProperty("userId", src.getAuthenticationHolder().getAuthentication().getName()); - o.add("expiration", context.serialize(src.getExpiration())); + o.add("expiration", context.serialize(src.getExpiration())); - return o; - } + return o; + } - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java index a9d588e12e..ee56889cc0 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/AuthenticationUtilities.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -25,14 +24,14 @@ import com.google.common.collect.ImmutableSet; /** - * + * * Utility class to enforce OAuth scopes in authenticated requests. - * + * * @author jricher * */ public abstract class AuthenticationUtilities { - + /** * Makes sure the authentication contains the given scope, throws an exception otherwise * @param auth the authentication object to check @@ -63,7 +62,7 @@ public static boolean isAdmin(Authentication auth) { } return false; } - + public static boolean hasRole(Authentication auth, String role) { for (GrantedAuthority grantedAuthority : auth.getAuthorities()) { @@ -72,7 +71,7 @@ public static boolean hasRole(Authentication auth, String role) { } } return false; - + } - + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java index c79e947ad9..da2aa69d12 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/CorsFilter.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.web; @@ -30,11 +31,11 @@ import org.springframework.web.filter.OncePerRequestFilter; /** - * + * * Implements Cross-Origin Resource Sharing (CORS) headers. This filter adds the CORS * headers to all requests that pass through it, and as such it should be used only * on endpoints that require CORS support. - * + * * @author jricher * */ diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/DeviceEndpoint.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/DeviceEndpoint.java new file mode 100644 index 0000000000..9c54c9f073 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/DeviceEndpoint.java @@ -0,0 +1,307 @@ +/******************************************************************************* + * 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.web; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import javax.servlet.http.HttpSession; + +import org.apache.http.client.utils.URIBuilder; +import org.mitre.oauth2.exception.DeviceCodeCreationException; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.DeviceCode; +import org.mitre.oauth2.model.SystemScope; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.oauth2.service.DeviceCodeService; +import org.mitre.oauth2.service.SystemScopeService; +import org.mitre.oauth2.token.DeviceTokenGranter; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.view.HttpCodeView; +import org.mitre.openid.connect.view.JsonEntityView; +import org.mitre.openid.connect.view.JsonErrorView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; +import org.springframework.security.oauth2.provider.AuthorizationRequest; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.security.oauth2.provider.OAuth2RequestFactory; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import com.google.common.collect.Sets; + +/** + * Implements https://tools.ietf.org/html/draft-ietf-oauth-device-flow + * + * @see DeviceTokenGranter + * + * @author jricher + * + */ +@Controller +public class DeviceEndpoint { + + public static final String URL = "devicecode"; + public static final String USER_URL = "device"; + + public static final Logger logger = LoggerFactory.getLogger(DeviceEndpoint.class); + + @Autowired + private ClientDetailsEntityService clientService; + + @Autowired + private SystemScopeService scopeService; + + @Autowired + private ConfigurationPropertiesBean config; + + @Autowired + private DeviceCodeService deviceCodeService; + + @Autowired + private OAuth2RequestFactory oAuth2RequestFactory; + + @RequestMapping(value = "/" + URL, method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) + public String requestDeviceCode(@RequestParam("client_id") String clientId, @RequestParam(name="scope", required=false) String scope, Map parameters, ModelMap model) { + + ClientDetailsEntity client; + try { + client = clientService.loadClientByClientId(clientId); + + // make sure this client can do the device flow + + Collection authorizedGrantTypes = client.getAuthorizedGrantTypes(); + if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty() + && !authorizedGrantTypes.contains(DeviceTokenGranter.GRANT_TYPE)) { + throw new InvalidClientException("Unauthorized grant type: " + DeviceTokenGranter.GRANT_TYPE); + } + + } catch (IllegalArgumentException e) { + logger.error("IllegalArgumentException was thrown when attempting to load client", e); + model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + return HttpCodeView.VIEWNAME; + } + + if (client == null) { + logger.error("could not find client " + clientId); + model.put(HttpCodeView.CODE, HttpStatus.NOT_FOUND); + return HttpCodeView.VIEWNAME; + } + + // make sure the client is allowed to ask for those scopes + Set requestedScopes = OAuth2Utils.parseParameterList(scope); + Set allowedScopes = client.getScope(); + + if (!scopeService.scopesMatch(allowedScopes, requestedScopes)) { + // client asked for scopes it can't have + logger.error("Client asked for " + requestedScopes + " but is allowed " + allowedScopes); + model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + model.put(JsonErrorView.ERROR, "invalid_scope"); + return JsonErrorView.VIEWNAME; + } + + // if we got here the request is legit + + try { + DeviceCode dc = deviceCodeService.createNewDeviceCode(requestedScopes, client, parameters); + + Map response = new HashMap<>(); + response.put("device_code", dc.getDeviceCode()); + response.put("user_code", dc.getUserCode()); + response.put("verification_uri", config.getIssuer() + USER_URL); + if (client.getDeviceCodeValiditySeconds() != null) { + response.put("expires_in", client.getDeviceCodeValiditySeconds()); + } + + if (config.isAllowCompleteDeviceCodeUri()) { + URI verificationUriComplete = new URIBuilder(config.getIssuer() + USER_URL) + .addParameter("user_code", dc.getUserCode()) + .build(); + + response.put("verification_uri_complete", verificationUriComplete.toString()); + } + + model.put(JsonEntityView.ENTITY, response); + + + return JsonEntityView.VIEWNAME; + } catch (DeviceCodeCreationException dcce) { + + model.put(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + model.put(JsonErrorView.ERROR, dcce.getError()); + model.put(JsonErrorView.ERROR_MESSAGE, dcce.getMessage()); + + return JsonErrorView.VIEWNAME; + } catch (URISyntaxException use) { + logger.error("unable to build verification_uri_complete due to wrong syntax of uri components"); + model.put(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); + + return HttpCodeView.VIEWNAME; + } + + } + + @PreAuthorize("hasRole('ROLE_USER')") + @RequestMapping(value = "/" + USER_URL, method = RequestMethod.GET) + public String requestUserCode(@RequestParam(value = "user_code", required = false) String userCode, ModelMap model, HttpSession session) { + + if (!config.isAllowCompleteDeviceCodeUri() || userCode == null) { + // if we don't allow the complete URI or we didn't get a user code on the way in, + // print out a page that asks the user to enter their user code + // user must be logged in + return "requestUserCode"; + } else { + + // complete verification uri was used, we received user code directly + // skip requesting code page + // user must be logged in + return readUserCode(userCode, model, session); + } + } + + @PreAuthorize("hasRole('ROLE_USER')") + @RequestMapping(value = "/" + USER_URL + "/verify", method = RequestMethod.POST) + public String readUserCode(@RequestParam("user_code") String userCode, ModelMap model, HttpSession session) { + + // look up the request based on the user code + DeviceCode dc = deviceCodeService.lookUpByUserCode(userCode); + + // we couldn't find the device code + if (dc == null) { + model.addAttribute("error", "noUserCode"); + return "requestUserCode"; + } + + // make sure the code hasn't expired yet + if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) { + model.addAttribute("error", "expiredUserCode"); + return "requestUserCode"; + } + + // make sure the device code hasn't already been approved + if (dc.isApproved()) { + model.addAttribute("error", "userCodeAlreadyApproved"); + return "requestUserCode"; + } + + ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId()); + + model.put("client", client); + model.put("dc", dc); + + // pre-process the scopes + Set scopes = scopeService.fromStrings(dc.getScope()); + + Set sortedScopes = new LinkedHashSet<>(scopes.size()); + Set systemScopes = scopeService.getAll(); + + // sort scopes for display based on the inherent order of system scopes + for (SystemScope s : systemScopes) { + if (scopes.contains(s)) { + sortedScopes.add(s); + } + } + + // add in any scopes that aren't system scopes to the end of the list + sortedScopes.addAll(Sets.difference(scopes, systemScopes)); + + model.put("scopes", sortedScopes); + + AuthorizationRequest authorizationRequest = oAuth2RequestFactory.createAuthorizationRequest(dc.getRequestParameters()); + + session.setAttribute("authorizationRequest", authorizationRequest); + session.setAttribute("deviceCode", dc); + + return "approveDevice"; + } + + @PreAuthorize("hasRole('ROLE_USER')") + @RequestMapping(value = "/" + USER_URL + "/approve", method = RequestMethod.POST) + public String approveDevice(@RequestParam("user_code") String userCode, @RequestParam(value = "user_oauth_approval") Boolean approve, ModelMap model, Authentication auth, HttpSession session) { + + AuthorizationRequest authorizationRequest = (AuthorizationRequest) session.getAttribute("authorizationRequest"); + DeviceCode dc = (DeviceCode) session.getAttribute("deviceCode"); + + // make sure the form that was submitted is the one that we were expecting + if (!dc.getUserCode().equals(userCode)) { + model.addAttribute("error", "userCodeMismatch"); + return "requestUserCode"; + } + + // make sure the code hasn't expired yet + if (dc.getExpiration() != null && dc.getExpiration().before(new Date())) { + model.addAttribute("error", "expiredUserCode"); + return "requestUserCode"; + } + + ClientDetailsEntity client = clientService.loadClientByClientId(dc.getClientId()); + + model.put("client", client); + + // user did not approve + if (!approve) { + model.addAttribute("approved", false); + return "deviceApproved"; + } + + // create an OAuth request for storage + OAuth2Request o2req = oAuth2RequestFactory.createOAuth2Request(authorizationRequest); + OAuth2Authentication o2Auth = new OAuth2Authentication(o2req, auth); + + DeviceCode approvedCode = deviceCodeService.approveDeviceCode(dc, o2Auth); + + // pre-process the scopes + Set scopes = scopeService.fromStrings(dc.getScope()); + + Set sortedScopes = new LinkedHashSet<>(scopes.size()); + Set systemScopes = scopeService.getAll(); + + // sort scopes for display based on the inherent order of system scopes + for (SystemScope s : systemScopes) { + if (scopes.contains(s)) { + sortedScopes.add(s); + } + } + + // add in any scopes that aren't system scopes to the end of the list + sortedScopes.addAll(Sets.difference(scopes, systemScopes)); + + model.put("scopes", sortedScopes); + model.put("approved", true); + + return "deviceApproved"; + } +} diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java index 1762323b84..7725a54030 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/IntrospectionEndpoint.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,8 @@ *******************************************************************************/ package org.mitre.oauth2.web; +import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope; + import java.util.Collection; import java.util.HashSet; import java.util.Map; @@ -49,13 +52,11 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; -import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope; - @Controller public class IntrospectionEndpoint { /** - * + * */ public static final String URL = "introspect"; @@ -70,7 +71,7 @@ public class IntrospectionEndpoint { @Autowired private UserInfoService userInfoService; - + @Autowired private ResourceSetService resourceSetService; @@ -94,52 +95,52 @@ public String verify(@RequestParam("token") String tokenValue, ClientDetailsEntity authClient = null; Set authScopes = new HashSet<>(); - + if (auth instanceof OAuth2Authentication) { // the client authenticated with OAuth, do our UMA checks ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); - + // get out the client that was issued the access token (not the token being introspected) OAuth2Authentication o2a = (OAuth2Authentication) auth; - + String authClientId = o2a.getOAuth2Request().getClientId(); authClient = clientService.loadClientByClientId(authClientId); - + // the owner is the user who authorized the token in the first place String ownerId = o2a.getUserAuthentication().getName(); - + authScopes.addAll(authClient.getScope()); - + // UMA style clients also get a subset of scopes of all the resource sets they've registered Collection resourceSets = resourceSetService.getAllForOwnerAndClient(ownerId, authClientId); - + // collect all the scopes for (ResourceSet rs : resourceSets) { authScopes.addAll(rs.getScopes()); } - + } else { // the client authenticated directly, make sure it's got the right access - + String authClientId = auth.getName(); // direct authentication puts the client_id into the authentication's name field authClient = clientService.loadClientByClientId(authClientId); // directly authenticated clients get a subset of any scopes that they've registered for authScopes.addAll(authClient.getScope()); - + if (!AuthenticationUtilities.hasRole(auth, "ROLE_CLIENT") || !authClient.isAllowIntrospection()) { - + // this client isn't allowed to do direct introspection - + logger.error("Client " + authClient.getClientId() + " is not allowed to call introspection endpoint"); model.addAttribute("code", HttpStatus.FORBIDDEN); return HttpCodeView.VIEWNAME; } - + } - + // by here we're allowed to introspect, now we need to look up the token in our token stores // first make sure the token is there @@ -188,7 +189,7 @@ public String verify(@RequestParam("token") String tokenValue, } // if it's a valid token, we'll print out information on it - + if (accessToken != null) { Map entity = introspectionResultAssembler.assembleFrom(accessToken, user, authScopes); model.addAttribute(JsonEntityView.ENTITY, entity); @@ -202,9 +203,9 @@ public String verify(@RequestParam("token") String tokenValue, model.addAttribute(JsonEntityView.ENTITY, entity); return JsonEntityView.VIEWNAME; } - + return JsonEntityView.VIEWNAME; - + } - + } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java index 35efd5c7c8..2a361cffde 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuth2ExceptionHandler.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -28,7 +27,7 @@ /** * Controller helper that handles OAuth2 exceptions and propagates them as JSON errors. - * + * * @author jricher * */ diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java index 719e4ee9e2..29c9a1419e 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/OAuthConfirmationController.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,13 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.web; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; + import java.net.URISyntaxException; import java.security.Principal; import java.util.Date; @@ -57,10 +61,6 @@ import com.google.common.collect.Sets; import com.google.gson.JsonObject; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CSRF; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; - /** * @author jricher * @@ -103,9 +103,9 @@ public OAuthConfirmationController(ClientDetailsEntityService clientService) { @PreAuthorize("hasRole('ROLE_USER')") @RequestMapping("/oauth/confirm_access") - public String confimAccess(Map model, @ModelAttribute("authorizationRequest") AuthorizationRequest authRequest, - Principal p) { + public String confirmAccess(Map model, Principal p) { + AuthorizationRequest authRequest = (AuthorizationRequest) model.get("authorizationRequest"); // Check the "prompt" parameter to see if we need to do special processing String prompt = (String)authRequest.getExtensions().get(PROMPT); @@ -131,20 +131,20 @@ public String confimAccess(Map model, @ModelAttribute("authoriza } if (prompts.contains("none")) { - // if we've got a redirect URI then we'll send it - + // if we've got a redirect URI then we'll send it + String url = redirectResolver.resolveRedirect(authRequest.getRedirectUri(), client); - + try { URIBuilder uriBuilder = new URIBuilder(url); - + uriBuilder.addParameter("error", "interaction_required"); if (!Strings.isNullOrEmpty(authRequest.getState())) { uriBuilder.addParameter("state", authRequest.getState()); // copy the state parameter if one was given } return "redirect:" + uriBuilder.toString(); - + } catch (URISyntaxException e) { logger.error("Can't build redirect URI for prompt=none, sending error instead", e); model.put("code", HttpStatus.FORBIDDEN); @@ -202,7 +202,7 @@ public String confimAccess(Map model, @ModelAttribute("authoriza model.put("claims", claimsForScopes); // client stats - Integer count = statsService.getCountForClientId(client.getId()); + Integer count = statsService.getCountForClientId(client.getClientId()).getApprovedSiteCount(); model.put("count", count); @@ -221,9 +221,6 @@ public String confimAccess(Map model, @ModelAttribute("authoriza model.put("gras", false); } - // inject a random value for CSRF purposes - model.put("csrf", authRequest.getExtensions().get(CSRF)); - return "approve"; } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java index a8d3330689..dd202fe9f7 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/RevocationEndpoint.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,20 +17,23 @@ *******************************************************************************/ package org.mitre.oauth2.web; -import java.security.Principal; +import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope; +import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.OAuth2TokenEntityService; +import org.mitre.oauth2.service.SystemScopeService; import org.mitre.openid.connect.view.HttpCodeView; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.InvalidTokenException; import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @@ -38,7 +42,10 @@ @Controller public class RevocationEndpoint { @Autowired - OAuth2TokenEntityService tokenServices; + private ClientDetailsEntityService clientService; + + @Autowired + private OAuth2TokenEntityService tokenServices; /** * Logger for this class @@ -49,32 +56,53 @@ public class RevocationEndpoint { @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") @RequestMapping("/" + URL) - public String revoke(@RequestParam("token") String tokenValue, @RequestParam(value = "token_type_hint", required = false) String tokenType, Principal principal, Model model) { + public String revoke(@RequestParam("token") String tokenValue, @RequestParam(value = "token_type_hint", required = false) String tokenType, Authentication auth, Model model) { // This is the token as passed in from OAuth (in case we need it some day) //OAuth2AccessTokenEntity tok = tokenServices.getAccessToken((OAuth2Authentication) principal); - OAuth2Request authRequest = null; - if (principal instanceof OAuth2Authentication) { - // if the client is acting on its own behalf (the common case), pull out the client authorization request - authRequest = ((OAuth2Authentication) principal).getOAuth2Request(); + ClientDetailsEntity authClient = null; + + if (auth instanceof OAuth2Authentication) { + // the client authenticated with OAuth, do our UMA checks + ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); + // get out the client that was issued the access token (not the token being revoked) + OAuth2Authentication o2a = (OAuth2Authentication) auth; + + String authClientId = o2a.getOAuth2Request().getClientId(); + authClient = clientService.loadClientByClientId(authClientId); + + // the owner is the user who authorized the token in the first place + String ownerId = o2a.getUserAuthentication().getName(); + + } else { + // the client authenticated directly, make sure it's got the right access + + String authClientId = auth.getName(); // direct authentication puts the client_id into the authentication's name field + authClient = clientService.loadClientByClientId(authClientId); + } try { // check and handle access tokens first OAuth2AccessTokenEntity accessToken = tokenServices.readAccessToken(tokenValue); - if (authRequest != null) { - // client acting on its own, make sure it owns the token - if (!accessToken.getClient().getClientId().equals(authRequest.getClientId())) { - // trying to revoke a token we don't own, throw a 403 - model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); - return HttpCodeView.VIEWNAME; - } + + // client acting on its own, make sure it owns the token + if (!accessToken.getClient().getClientId().equals(authClient.getClientId())) { + // trying to revoke a token we don't own, throw a 403 + + logger.info("Client " + authClient.getClientId() + " tried to revoke a token owned by " + accessToken.getClient().getClientId()); + + model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); + return HttpCodeView.VIEWNAME; } // if we got this far, we're allowed to do this tokenServices.revokeAccessToken(accessToken); + + logger.debug("Client " + authClient.getClientId() + " revoked access token " + tokenValue); + model.addAttribute(HttpCodeView.CODE, HttpStatus.OK); return HttpCodeView.VIEWNAME; @@ -84,17 +112,21 @@ public String revoke(@RequestParam("token") String tokenValue, @RequestParam(val try { OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(tokenValue); - if (authRequest != null) { - // client acting on its own, make sure it owns the token - if (!refreshToken.getClient().getClientId().equals(authRequest.getClientId())) { - // trying to revoke a token we don't own, throw a 403 - model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); - return HttpCodeView.VIEWNAME; - } + // client acting on its own, make sure it owns the token + if (!refreshToken.getClient().getClientId().equals(authClient.getClientId())) { + // trying to revoke a token we don't own, throw a 403 + + logger.info("Client " + authClient.getClientId() + " tried to revoke a token owned by " + refreshToken.getClient().getClientId()); + + model.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); + return HttpCodeView.VIEWNAME; } // if we got this far, we're allowed to do this tokenServices.revokeRefreshToken(refreshToken); + + logger.debug("Client " + authClient.getClientId() + " revoked access token " + tokenValue); + model.addAttribute(HttpCodeView.CODE, HttpStatus.OK); return HttpCodeView.VIEWNAME; @@ -102,6 +134,8 @@ public String revoke(@RequestParam("token") String tokenValue, @RequestParam(val // neither token type was found, simply say "OK" and be on our way. + logger.debug("Failed to revoke token " + tokenValue); + model.addAttribute(HttpCodeView.CODE, HttpStatus.OK); return HttpCodeView.VIEWNAME; } diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java index 70ed8f2a19..5aa6d2a3b1 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/ScopeAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.oauth2.web; @@ -52,7 +53,7 @@ public class ScopeAPI { public static final String URL = RootController.API_URL + "/scopes"; - + @Autowired private SystemScopeService scopeService; diff --git a/openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java b/openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java index 567ab7fcb2..73fa472b47 100644 --- a/openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/oauth2/web/TokenAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java index 383b1e25da..bc4f9abcde 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAssertionAuthenticationToken.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,11 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.assertion; +import java.text.ParseException; import java.util.Collection; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -33,33 +35,45 @@ public class JWTBearerAssertionAuthenticationToken extends AbstractAuthenticationToken { /** - * + * */ private static final long serialVersionUID = -3138213539914074617L; - private String clientId; + private String subject; private JWT jwt; /** - * Create an unauthenticated token with the given client ID and jwt - * @param clientId + * Create an unauthenticated token with the given subject and jwt + * @param subject * @param jwt */ - public JWTBearerAssertionAuthenticationToken(String clientId, JWT jwt) { + public JWTBearerAssertionAuthenticationToken(JWT jwt) { super(null); - this.clientId = clientId; + try { + // save the subject of the JWT in case the credentials get erased later + this.subject = jwt.getJWTClaimsSet().getSubject(); + } catch (ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } this.jwt = jwt; setAuthenticated(false); } /** * Create an authenticated token with the given clientID, jwt, and authorities set - * @param clientId + * @param subject * @param jwt * @param authorities */ - public JWTBearerAssertionAuthenticationToken(String clientId, JWT jwt, Collection authorities) { + public JWTBearerAssertionAuthenticationToken(JWT jwt, Collection authorities) { super(authorities); - this.clientId = clientId; + try { + // save the subject of the JWT in case the credentials get erased later + this.subject = jwt.getJWTClaimsSet().getSubject(); + } catch (ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } this.jwt = jwt; setAuthenticated(true); } @@ -77,21 +91,7 @@ public Object getCredentials() { */ @Override public Object getPrincipal() { - return clientId; - } - - /** - * @return the clientId - */ - public String getClientId() { - return clientId; - } - - /** - * @param clientId the clientId to set - */ - public void setClientId(String clientId) { - this.clientId = clientId; + return subject; } /** diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java index 7ef8ab7184..749a8edddc 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerAuthenticationProvider.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.assertion; @@ -44,7 +45,7 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jwt.JWT; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; /** @@ -85,57 +86,65 @@ public Authentication authenticate(Authentication authentication) throws Authent try { - ClientDetailsEntity client = clientService.loadClientByClientId(jwtAuth.getClientId()); + ClientDetailsEntity client = clientService.loadClientByClientId(jwtAuth.getName()); JWT jwt = jwtAuth.getJwt(); - ReadOnlyJWTClaimsSet jwtClaims = jwt.getJWTClaimsSet(); + JWTClaimsSet jwtClaims = jwt.getJWTClaimsSet(); + + if (!(jwt instanceof SignedJWT)) { + throw new AuthenticationServiceException("Unsupported JWT type: " + jwt.getClass().getName()); + } // check the signature with nimbus - if (jwt instanceof SignedJWT) { - SignedJWT jws = (SignedJWT)jwt; + SignedJWT jws = (SignedJWT) jwt; - JWSAlgorithm alg = jws.getHeader().getAlgorithm(); + JWSAlgorithm alg = jws.getHeader().getAlgorithm(); - if (client.getTokenEndpointAuthSigningAlg() != null && - !client.getTokenEndpointAuthSigningAlg().equals(alg)) { - throw new InvalidClientException("Client's registered request object signing algorithm (" + client.getRequestObjectSigningAlg() + ") does not match request object's actual algorithm (" + alg.getName() + ")"); - } + if (client.getTokenEndpointAuthSigningAlg() != null && + !client.getTokenEndpointAuthSigningAlg().equals(alg)) { + throw new AuthenticationServiceException("Client's registered token endpoint signing algorithm (" + client.getTokenEndpointAuthSigningAlg() + + ") does not match token's actual algorithm (" + alg.getName() + ")"); + } - if (client.getTokenEndpointAuthMethod() == null || - client.getTokenEndpointAuthMethod().equals(AuthMethod.NONE) || - client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC) || - client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)) { - - // this client doesn't support this type of authentication - throw new AuthenticationServiceException("Client does not support this authentication method."); - - } else if ((client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) && - (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))) + if (client.getTokenEndpointAuthMethod() == null || + client.getTokenEndpointAuthMethod().equals(AuthMethod.NONE) || + client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_BASIC) || + client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_POST)) { + + // this client doesn't support this type of authentication + throw new AuthenticationServiceException("Client does not support this authentication method."); + + } else if ((client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY) && + (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))) || (client.getTokenEndpointAuthMethod().equals(AuthMethod.SECRET_JWT) && - (alg.equals(JWSAlgorithm.HS256) - || alg.equals(JWSAlgorithm.HS384) - || alg.equals(JWSAlgorithm.HS512)))) { + (alg.equals(JWSAlgorithm.HS256) + || alg.equals(JWSAlgorithm.HS384) + || alg.equals(JWSAlgorithm.HS512)))) { - JWTSigningAndValidationService validator = validators.getValidator(client, alg); + // double-check the method is asymmetrical if we're in HEART mode + if (config.isHeartMode() && !client.getTokenEndpointAuthMethod().equals(AuthMethod.PRIVATE_KEY)) { + throw new AuthenticationServiceException("[HEART mode] Invalid authentication method"); + } - if (validator == null) { - throw new AuthenticationServiceException("Unable to create signature validator for client " + client + " and algorithm " + alg); - } + JWTSigningAndValidationService validator = validators.getValidator(client, alg); - if (!validator.validateSignature(jws)) { - throw new AuthenticationServiceException("Signature did not validate for presented JWT authentication."); - } - } else { - throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg); + if (validator == null) { + throw new AuthenticationServiceException("Unable to create signature validator for client " + client + " and algorithm " + alg); } + + if (!validator.validateSignature(jws)) { + throw new AuthenticationServiceException("Signature did not validate for presented JWT authentication."); + } + } else { + throw new AuthenticationServiceException("Unable to create signature validator for method " + client.getTokenEndpointAuthMethod() + " and algorithm " + alg); } // check the issuer @@ -186,10 +195,10 @@ public Authentication authenticate(Authentication authentication) throws Authent Set authorities = new HashSet<>(client.getAuthorities()); authorities.add(ROLE_CLIENT); - return new JWTBearerAssertionAuthenticationToken(client.getClientId(), jwt, authorities); + return new JWTBearerAssertionAuthenticationToken(jwt, authorities); } catch (InvalidClientException e) { - throw new UsernameNotFoundException("Could not find client: " + jwtAuth.getClientId()); + throw new UsernameNotFoundException("Could not find client: " + jwtAuth.getName()); } catch (ParseException e) { logger.error("Failure during authentication, error was: ", e); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java index 2520775771..e1e5ca9060 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/assertion/JWTBearerClientAssertionTokenEndpointFilter.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.assertion; @@ -44,7 +45,7 @@ /** * Filter to check client authentication via JWT Bearer assertions. - * + * * @author jricher * */ @@ -62,6 +63,7 @@ public JWTBearerClientAssertionTokenEndpointFilter(RequestMatcher additionalMatc public void afterPropertiesSet() { super.afterPropertiesSet(); setAuthenticationFailureHandler(new AuthenticationFailureHandler() { + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { if (exception instanceof BadCredentialsException) { @@ -71,6 +73,7 @@ public void onAuthenticationFailure(HttpServletRequest request, HttpServletRespo } }); setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() { + @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // no-op - just allow filter chain to continue to token endpoint @@ -93,7 +96,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ String clientId = jwt.getJWTClaimsSet().getSubject(); - Authentication authRequest = new JWTBearerAssertionAuthenticationToken(clientId, jwt); + Authentication authRequest = new JWTBearerAssertionAuthenticationToken(jwt); return this.getAuthenticationManager().authenticate(authRequest); } catch (ParseException e) { @@ -109,13 +112,13 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR } private static class ClientAssertionRequestMatcher implements RequestMatcher { - + private RequestMatcher additionalMatcher; - + public ClientAssertionRequestMatcher(RequestMatcher additionalMatcher) { this.additionalMatcher = additionalMatcher; } - + @Override public boolean matches(HttpServletRequest request) { // check for appropriate parameters @@ -127,10 +130,10 @@ public boolean matches(HttpServletRequest request) { } else if (!assertionType.equals("urn:ietf:params:oauth:client-assertion-type:jwt-bearer")) { return false; } - + return additionalMatcher.matches(request); } - + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java index 8c5a7303b1..10746d3dd2 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/config/JsonMessageSource.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,16 +17,20 @@ package org.mitre.openid.connect.config; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.text.MessageFormat; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Locale; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.support.AbstractMessageSource; import org.springframework.core.io.Resource; @@ -40,114 +43,151 @@ /** * @author jricher - * */ public class JsonMessageSource extends AbstractMessageSource { - // Logger for this class + private static final Logger logger = LoggerFactory.getLogger(JsonMessageSource.class); private Resource baseDirectory; - + private Locale fallbackLocale = new Locale("en"); // US English is the fallback language - private Map languageMaps = new HashMap<>(); - + private Map> languageMaps = new HashMap<>(); + + @Autowired + private ConfigurationPropertiesBean config; + @Override protected MessageFormat resolveCode(String code, Locale locale) { - - JsonObject lang = getLanguageMap(locale); - String value = getValue(code, lang); - + List langs = getLanguageMap(locale); + + String value = getValue(code, langs); + if (value == null) { // if we haven't found anything, try the default locale - lang = getLanguageMap(fallbackLocale); - value = getValue(code, lang); + langs = getLanguageMap(fallbackLocale); + value = getValue(code, langs); } - + if (value == null) { - value = code; + // if it's still null, return null + return null; + } else { + // otherwise format the message + return new MessageFormat(value, locale); } - MessageFormat mf = new MessageFormat(value, locale); - - return mf; } /** + * Get a value from the set of maps, taking the first match in order + * @param code + * @param langs + * @return + */ + private String getValue(String code, List langs) { + if (langs == null || langs.isEmpty()) { + // no language maps, nothing to look up + return null; + } + + for (JsonObject lang : langs) { + String value = getValue(code, lang); + if (value != null) { + // short circuit out of here if we find a match, otherwise keep going through the list + return value; + } + } + + // if we didn't find anything return null + return null; + } + + /** + * Get a value from a single map * @param code - * @param locale * @param lang * @return */ private String getValue(String code, JsonObject lang) { - + // if there's no language map, nothing to look up if (lang == null) { return null; } - + JsonElement e = lang; - + Iterable parts = Splitter.on('.').split(code); Iterator it = parts.iterator(); - + String value = null; - + while (it.hasNext()) { String p = it.next(); - if (e.isJsonObject()) { - JsonObject o = e.getAsJsonObject(); - if (o.has(p)) { - e = o.get(p); // found the next level - if (!it.hasNext()) { - // we've reached a leaf, grab it - if (e.isJsonPrimitive()) { - value = e.getAsString(); - } + if (e.isJsonObject()) { + JsonObject o = e.getAsJsonObject(); + if (o.has(p)) { + e = o.get(p); // found the next level + if (!it.hasNext()) { + // we've reached a leaf, grab it + if (e.isJsonPrimitive()) { + value = e.getAsString(); } - } else { - // didn't find it, stop processing - break; } } else { // didn't find it, stop processing break; } + } else { + // didn't find it, stop processing + break; + } } - - + return value; - } /** * @param locale * @return */ - private JsonObject getLanguageMap(Locale locale) { - + private List getLanguageMap(Locale locale) { + if (!languageMaps.containsKey(locale)) { try { - String filename = locale.getLanguage() + File.separator + "messages.json"; - - Resource r = getBaseDirectory().createRelative(filename); - - logger.info("No locale loaded, trying to load from " + r); - - JsonParser parser = new JsonParser(); - JsonObject obj = (JsonObject) parser.parse(new InputStreamReader(r.getInputStream(), "UTF-8")); - - languageMaps.put(locale, obj); + List set = new ArrayList<>(); + for (String namespace : config.getLanguageNamespaces()) { + // full locale string, e.g. "en_US" + String filename = locale.getLanguage() + "_" + locale.getCountry() + File.separator + namespace + ".json"; + + Resource r = getBaseDirectory().createRelative(filename); + + if (!r.exists()) { + // fallback to language only + logger.debug("Fallback locale to language only."); + filename = locale.getLanguage() + File.separator + namespace + ".json"; + r = getBaseDirectory().createRelative(filename); + } + + logger.info("No locale loaded, trying to load from {}", r); + + JsonParser parser = new JsonParser(); + JsonObject obj = (JsonObject) parser.parse(new InputStreamReader(r.getInputStream(), "UTF-8")); + + set.add(obj); + } + languageMaps.put(locale, set); + } catch (FileNotFoundException e) { + logger.info("Unable to load locale because no messages file was found for locale {}", locale.getDisplayName()); + languageMaps.put(locale, null); } catch (JsonIOException | JsonSyntaxException | IOException e) { logger.error("Unable to load locale", e); } } - + return languageMaps.get(locale); - - - } /** diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java index 5088464ce3..85efe2c87e 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/exception/ValidationException.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -60,4 +59,4 @@ public String toString() { return "ValidationException [error=" + error + ", errorDescription=" + errorDescription + ", status=" + status + "]"; } -} \ No newline at end of file +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java index f70b25e3e5..63e2390537 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/AuthorizationRequestFilter.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,20 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.filter; +import static org.mitre.openid.connect.request.ConnectRequestParameters.ERROR; +import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_REQUIRED; +import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_LOGIN; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_NONE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; +import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE; + import java.io.IOException; import java.net.URISyntaxException; import java.util.Date; @@ -37,6 +48,8 @@ import org.apache.http.client.utils.URIBuilder; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.openid.connect.service.LoginHintExtracter; +import org.mitre.openid.connect.service.impl.RemoveLoginHintsWithHTTP; import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,22 +60,14 @@ import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.OAuth2RequestFactory; import org.springframework.security.oauth2.provider.endpoint.RedirectResolver; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; +import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; import com.google.common.base.Splitter; import com.google.common.base.Strings; -import static org.mitre.openid.connect.request.ConnectRequestParameters.ERROR; -import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_REQUIRED; -import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_LOGIN; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_NONE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; -import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE; - /** * @author jricher * @@ -83,12 +88,17 @@ public class AuthorizationRequestFilter extends GenericFilterBean { @Autowired private ClientDetailsEntityService clientService; - + @Autowired private RedirectResolver redirectResolver; + @Autowired(required = false) + private LoginHintExtracter loginHintExtracter = new RemoveLoginHintsWithHTTP(); + + private RequestMatcher requestMatcher = new AntPathRequestMatcher("/authorize"); + /** - * + * */ @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { @@ -98,7 +108,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) HttpSession session = request.getSession(); // skip everything that's not an authorize URL - if (!request.getServletPath().startsWith("/authorize")) { + if (!requestMatcher.matches(request)) { chain.doFilter(req, res); return; } @@ -106,7 +116,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) try { // we have to create our own auth request in order to get at all the parmeters appropriately AuthorizationRequest authRequest = null; - + ClientDetailsEntity client = null; authRequest = authRequestFactory.createAuthorizationRequest(createRequestMap(request.getParameterMap())); @@ -115,21 +125,23 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) } // save the login hint to the session - if (authRequest.getExtensions().get(LOGIN_HINT) != null) { - session.setAttribute(LOGIN_HINT, authRequest.getExtensions().get(LOGIN_HINT)); + // but first check to see if the login hint makes any sense + String loginHint = loginHintExtracter.extractHint((String) authRequest.getExtensions().get(LOGIN_HINT)); + if (!Strings.isNullOrEmpty(loginHint)) { + session.setAttribute(LOGIN_HINT, loginHint); } else { session.removeAttribute(LOGIN_HINT); } - + if (authRequest.getExtensions().get(PROMPT) != null) { // we have a "prompt" parameter String prompt = (String)authRequest.getExtensions().get(PROMPT); List prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); - + if (prompts.contains(PROMPT_NONE)) { // see if the user's logged in Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - + if (auth != null) { // user's been logged in already (by session management) // we're OK, continue without prompting @@ -138,40 +150,40 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) logger.info("Client requested no prompt"); // user hasn't been logged in, we need to "return an error" if (client != null && authRequest.getRedirectUri() != null) { - - // if we've got a redirect URI then we'll send it - + + // if we've got a redirect URI then we'll send it + String url = redirectResolver.resolveRedirect(authRequest.getRedirectUri(), client); - + try { URIBuilder uriBuilder = new URIBuilder(url); - + uriBuilder.addParameter(ERROR, LOGIN_REQUIRED); if (!Strings.isNullOrEmpty(authRequest.getState())) { uriBuilder.addParameter(STATE, authRequest.getState()); // copy the state parameter if one was given } - + response.sendRedirect(uriBuilder.toString()); return; - + } catch (URISyntaxException e) { logger.error("Can't build redirect URI for prompt=none, sending error instead", e); response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); return; } } - + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied"); return; } } else if (prompts.contains(PROMPT_LOGIN)) { - + // first see if the user's already been prompted in this session if (session.getAttribute(PROMPTED) == null) { // user hasn't been PROMPTED yet, we need to check - + session.setAttribute(PROMPT_REQUESTED, Boolean.TRUE); - + // see if the user's logged in Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { @@ -185,7 +197,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) } } else { // user has been PROMPTED, we're fine - + // but first, undo the prompt tag session.removeAttribute(PROMPTED); chain.doFilter(req, res); @@ -194,21 +206,21 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) // prompt parameter is a value we don't care about, not our business chain.doFilter(req, res); } - + } else if (authRequest.getExtensions().get(MAX_AGE) != null || (client != null && client.getDefaultMaxAge() != null)) { - + // default to the client's stored value, check the string parameter Integer max = (client != null ? client.getDefaultMaxAge() : null); String maxAge = (String) authRequest.getExtensions().get(MAX_AGE); if (maxAge != null) { max = Integer.parseInt(maxAge); } - + if (max != null) { - + Date authTime = (Date) session.getAttribute(AuthenticationTimeStamper.AUTH_TIMESTAMP); - + Date now = new Date(); if (authTime != null) { long seconds = (now.getTime() - authTime.getTime()) / 1000; @@ -223,7 +235,7 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) // no prompt parameter, not our business chain.doFilter(req, res); } - + } catch (InvalidClientException e) { // we couldn't find the client, move on and let the rest of the system catch the error chain.doFilter(req, res); @@ -246,4 +258,18 @@ private Map createRequestMap(Map parameterMap) return requestMap; } + /** + * @return the requestMatcher + */ + public RequestMatcher getRequestMatcher() { + return requestMatcher; + } + + /** + * @param requestMatcher the requestMatcher to set + */ + public void setRequestMatcher(RequestMatcher requestMatcher) { + this.requestMatcher = requestMatcher; + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java index 9e62f43541..c2c140b560 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/filter/MultiUrlRequestMatcher.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,52 +16,37 @@ package org.mitre.openid.connect.filter; +import java.util.HashSet; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.springframework.security.web.util.UrlUtils; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import com.google.common.collect.ImmutableSet; - /** * @author jricher * */ public class MultiUrlRequestMatcher implements RequestMatcher { - private final Set filterProcessesUrls; + private final Set matchers; public MultiUrlRequestMatcher(Set filterProcessesUrls) { + this.matchers = new HashSet<>(filterProcessesUrls.size()); for (String filterProcessesUrl : filterProcessesUrls) { Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified"); - Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL"); + Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid URL"); + matchers.add(new AntPathRequestMatcher(filterProcessesUrl)); } - this.filterProcessesUrls = ImmutableSet.copyOf(filterProcessesUrls); + } + @Override public boolean matches(HttpServletRequest request) { - String uri = request.getRequestURI(); - int pathParamIndex = uri.indexOf(';'); - - if (pathParamIndex > 0) { - // strip everything after the first semi-colon - uri = uri.substring(0, pathParamIndex); - } - - if ("".equals(request.getContextPath())) { - // if any one of the URLs match, return true - for (String filterProcessesUrl : filterProcessesUrls) { - if (uri.endsWith(filterProcessesUrl)) { - return true; - } - } - return false; - } - - for (String filterProcessesUrl : filterProcessesUrls) { - if (uri.endsWith(request.getContextPath() + filterProcessesUrl)) { + for (RequestMatcher matcher : matchers) { + if (matcher.matches(request)) { return true; } } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java index ff7fbb97ed..c2556d62f1 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaAddressRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -26,18 +27,18 @@ /** * JPA Address repository implementation - * + * * @author Michael Joseph Walsh - * + * */ @Repository public class JpaAddressRepository implements AddressRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Address getById(Long id) { return manager.find(Address.class, id); } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java index 330116e230..de30df5130 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaApprovedSiteRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,8 @@ *******************************************************************************/ package org.mitre.openid.connect.repository.impl; +import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; + import java.util.Collection; import javax.persistence.EntityManager; @@ -27,35 +30,33 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; - /** * JPA ApprovedSite repository implementation - * + * * @author Michael Joseph Walsh, aanganes * */ @Repository public class JpaApprovedSiteRepository implements ApprovedSiteRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Collection getAll() { TypedQuery query = manager.createNamedQuery(ApprovedSite.QUERY_ALL, ApprovedSite.class); return query.getResultList(); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public ApprovedSite getById(Long id) { return manager.find(ApprovedSite.class, id); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(ApprovedSite approvedSite) { ApprovedSite found = manager.find(ApprovedSite.class, approvedSite.getId()); @@ -67,7 +68,7 @@ public void remove(ApprovedSite approvedSite) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public ApprovedSite save(ApprovedSite approvedSite) { return saveOrUpdate(approvedSite.getId(), manager, approvedSite); } @@ -83,7 +84,7 @@ public Collection getByClientIdAndUserId(String clientId, String u } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Collection getByUserId(String userId) { TypedQuery query = manager.createNamedQuery(ApprovedSite.QUERY_BY_USER_ID, ApprovedSite.class); query.setParameter(ApprovedSite.PARAM_USER_ID, userId); @@ -93,7 +94,7 @@ public Collection getByUserId(String userId) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Collection getByClientId(String clientId) { TypedQuery query = manager.createNamedQuery(ApprovedSite.QUERY_BY_CLIENT_ID, ApprovedSite.class); query.setParameter(ApprovedSite.PARAM_CLIENT_ID, clientId); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java index 762a578241..b56a137c42 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaBlacklistedSiteRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,12 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.repository.impl; +import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; + import java.util.Collection; import javax.persistence.EntityManager; @@ -30,8 +33,6 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; - /** * @author jricher * @@ -39,14 +40,14 @@ @Repository public class JpaBlacklistedSiteRepository implements BlacklistedSiteRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; /* (non-Javadoc) * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#getAll() */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Collection getAll() { TypedQuery query = manager.createNamedQuery(BlacklistedSite.QUERY_ALL, BlacklistedSite.class); return query.getResultList(); @@ -56,7 +57,7 @@ public Collection getAll() { * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#getById(java.lang.Long) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public BlacklistedSite getById(Long id) { return manager.find(BlacklistedSite.class, id); } @@ -65,7 +66,7 @@ public BlacklistedSite getById(Long id) { * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#remove(org.mitre.openid.connect.model.BlacklistedSite) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(BlacklistedSite blacklistedSite) { BlacklistedSite found = manager.find(BlacklistedSite.class, blacklistedSite.getId()); @@ -81,7 +82,7 @@ public void remove(BlacklistedSite blacklistedSite) { * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#save(org.mitre.openid.connect.model.BlacklistedSite) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public BlacklistedSite save(BlacklistedSite blacklistedSite) { return saveOrUpdate(blacklistedSite.getId(), manager, blacklistedSite); } @@ -90,7 +91,7 @@ public BlacklistedSite save(BlacklistedSite blacklistedSite) { * @see org.mitre.openid.connect.repository.BlacklistedSiteRepository#update(org.mitre.openid.connect.model.BlacklistedSite, org.mitre.openid.connect.model.BlacklistedSite) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public BlacklistedSite update(BlacklistedSite oldBlacklistedSite, BlacklistedSite blacklistedSite) { blacklistedSite.setId(oldBlacklistedSite.getId()); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java index 658517e75a..a92acf3bd6 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaPairwiseIdentifierRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,10 +16,13 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.repository.impl; +import static org.mitre.util.jpa.JpaUtil.getSingleResult; +import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; + import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; @@ -28,9 +32,6 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import static org.mitre.util.jpa.JpaUtil.getSingleResult; -import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; - /** * @author jricher * @@ -38,7 +39,7 @@ @Repository public class JpaPairwiseIdentifierRepository implements PairwiseIdentifierRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; /* (non-Javadoc) @@ -57,7 +58,7 @@ public PairwiseIdentifier getBySectorIdentifier(String sub, String sectorIdentif * @see org.mitre.openid.connect.repository.PairwiseIdentifierRepository#save(org.mitre.openid.connect.model.PairwiseIdentifier) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void save(PairwiseIdentifier pairwise) { saveOrUpdate(pairwise.getId(), manager, pairwise); } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java index 59fb332c4c..7627246da3 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaUserInfoRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,8 @@ *******************************************************************************/ package org.mitre.openid.connect.repository.impl; +import static org.mitre.util.jpa.JpaUtil.getSingleResult; + import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; @@ -25,18 +28,16 @@ import org.mitre.openid.connect.repository.UserInfoRepository; import org.springframework.stereotype.Repository; -import static org.mitre.util.jpa.JpaUtil.getSingleResult; - /** * JPA UserInfo repository implementation - * + * * @author Michael Joseph Walsh * */ @Repository("jpaUserInfoRepository") public class JpaUserInfoRepository implements UserInfoRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; /** @@ -58,7 +59,7 @@ public UserInfo getByUsername(String username) { public UserInfo getByEmailAddress(String email) { TypedQuery query = manager.createNamedQuery(DefaultUserInfo.QUERY_BY_EMAIL, DefaultUserInfo.class); query.setParameter(DefaultUserInfo.PARAM_EMAIL, email); - + return getSingleResult(query.getResultList()); } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java index c26c03917c..98173588b7 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/repository/impl/JpaWhitelistedSiteRepository.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,8 @@ *******************************************************************************/ package org.mitre.openid.connect.repository.impl; +import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; + import java.util.Collection; import javax.persistence.EntityManager; @@ -28,35 +31,33 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import static org.mitre.util.jpa.JpaUtil.saveOrUpdate; - /** * JPA WhitelistedSite repository implementation - * + * * @author Michael Joseph Walsh, aanganes * */ @Repository public class JpaWhitelistedSiteRepository implements WhitelistedSiteRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager manager; @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Collection getAll() { TypedQuery query = manager.createNamedQuery(WhitelistedSite.QUERY_ALL, WhitelistedSite.class); return query.getResultList(); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public WhitelistedSite getById(Long id) { return manager.find(WhitelistedSite.class, id); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(WhitelistedSite whitelistedSite) { WhitelistedSite found = manager.find(WhitelistedSite.class, whitelistedSite.getId()); @@ -68,13 +69,13 @@ public void remove(WhitelistedSite whitelistedSite) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public WhitelistedSite save(WhitelistedSite whiteListedSite) { return saveOrUpdate(whiteListedSite.getId(), manager, whiteListedSite); } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public WhitelistedSite update(WhitelistedSite oldWhitelistedSite, WhitelistedSite whitelistedSite) { // sanity check whitelistedSite.setId(oldWhitelistedSite.getId()); @@ -83,7 +84,7 @@ public WhitelistedSite update(WhitelistedSite oldWhitelistedSite, WhitelistedSit } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public WhitelistedSite getByClientId(String clientId) { TypedQuery query = manager.createNamedQuery(WhitelistedSite.QUERY_BY_CLIENT_ID, WhitelistedSite.class); query.setParameter(WhitelistedSite.PARAM_CLIENT_ID, clientId); @@ -91,7 +92,7 @@ public WhitelistedSite getByClientId(String clientId) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Collection getByCreator(String creatorId) { TypedQuery query = manager.createNamedQuery(WhitelistedSite.QUERY_BY_CREATOR, WhitelistedSite.class); query.setParameter(WhitelistedSite.PARAM_USER_ID, creatorId); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java index 74e843d5cb..d957f31a4f 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectOAuth2RequestFactory.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,18 +18,34 @@ package org.mitre.openid.connect.request; +import static org.mitre.openid.connect.request.ConnectRequestParameters.AUD; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CLAIMS; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CLIENT_ID; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.CODE_CHALLENGE_METHOD; +import static org.mitre.openid.connect.request.ConnectRequestParameters.DISPLAY; +import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.REDIRECT_URI; +import static org.mitre.openid.connect.request.ConnectRequestParameters.REQUEST; +import static org.mitre.openid.connect.request.ConnectRequestParameters.RESPONSE_TYPE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.SCOPE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE; + +import java.io.Serializable; import java.text.ParseException; import java.util.Collections; import java.util.Map; import java.util.Set; -import java.util.UUID; import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.PKCEAlgorithm; import org.mitre.oauth2.service.ClientDetailsEntityService; -import org.mitre.oauth2.service.SystemScopeService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -48,25 +65,11 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jwt.EncryptedJWT; import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.PlainJWT; -import com.nimbusds.jwt.ReadOnlyJWTClaimsSet; import com.nimbusds.jwt.SignedJWT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CLAIMS; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CLIENT_ID; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CSRF; -import static org.mitre.openid.connect.request.ConnectRequestParameters.DISPLAY; -import static org.mitre.openid.connect.request.ConnectRequestParameters.LOGIN_HINT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.REDIRECT_URI; -import static org.mitre.openid.connect.request.ConnectRequestParameters.REQUEST; -import static org.mitre.openid.connect.request.ConnectRequestParameters.RESPONSE_TYPE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.SCOPE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.STATE; - @Component("connectOAuth2RequestFactory") public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { @@ -80,9 +83,6 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { @Autowired private ClientKeyCacheService validators; - @Autowired - private SystemScopeService systemScopes; - @Autowired private JWTEncryptionAndDecryptionService encryptionService; @@ -90,9 +90,8 @@ public class ConnectOAuth2RequestFactory extends DefaultOAuth2RequestFactory { /** * Constructor with arguments - * + * * @param clientDetailsService - * @param nonceService */ @Autowired public ConnectOAuth2RequestFactory(ClientDetailsEntityService clientDetailsService) { @@ -135,6 +134,21 @@ public AuthorizationRequest createAuthorizationRequest(Map input request.getExtensions().put(LOGIN_HINT, inputParams.get(LOGIN_HINT)); } + if (inputParams.containsKey(AUD)) { + request.getExtensions().put(AUD, inputParams.get(AUD)); + } + + if (inputParams.containsKey(CODE_CHALLENGE)) { + request.getExtensions().put(CODE_CHALLENGE, inputParams.get(CODE_CHALLENGE)); + if (inputParams.containsKey(CODE_CHALLENGE_METHOD)) { + request.getExtensions().put(CODE_CHALLENGE_METHOD, inputParams.get(CODE_CHALLENGE_METHOD)); + } else { + // if the client doesn't specify a code challenge transformation method, it's "plain" + request.getExtensions().put(CODE_CHALLENGE_METHOD, PKCEAlgorithm.plain.getName()); + } + + } + if (inputParams.containsKey(REQUEST)) { request.getExtensions().put(REQUEST, inputParams.get(REQUEST)); processRequestObject(inputParams.get(REQUEST), request); @@ -157,19 +171,13 @@ public AuthorizationRequest createAuthorizationRequest(Map input } } - - // add CSRF protection to the request on first parse - String csrf = UUID.randomUUID().toString(); - request.getExtensions().put(CSRF, csrf); - - - return request; } /** - * @param inputParams - * @return + * + * @param jwtString + * @param request */ private void processRequestObject(String jwtString, AuthorizationRequest request) { @@ -268,10 +276,10 @@ private void processRequestObject(String jwtString, AuthorizationRequest request // now that we've got the JWT, and it's been parsed, validated, and/or decrypted, we can process the claims - ReadOnlyJWTClaimsSet claims = jwt.getJWTClaimsSet(); + JWTClaimsSet claims = jwt.getJWTClaimsSet(); Set responseTypes = OAuth2Utils.parseParameterList(claims.getStringClaim(RESPONSE_TYPE)); - if (responseTypes != null && !responseTypes.isEmpty()) { + if (!responseTypes.isEmpty()) { if (!responseTypes.equals(request.getResponseTypes())) { logger.info("Mismatch between request object and regular parameter for response_type, using request object"); } @@ -319,7 +327,7 @@ private void processRequestObject(String jwtString, AuthorizationRequest request } Set scope = OAuth2Utils.parseParameterList(claims.getStringClaim(SCOPE)); - if (scope != null && !scope.isEmpty()) { + if (!scope.isEmpty()) { if (!scope.equals(request.getScope())) { logger.info("Mismatch between request object and regular parameter for scope, using request object"); } @@ -328,7 +336,8 @@ private void processRequestObject(String jwtString, AuthorizationRequest request JsonObject claimRequest = parseClaimRequest(claims.getStringClaim(CLAIMS)); if (claimRequest != null) { - if (!claimRequest.equals(parseClaimRequest(request.getExtensions().get(CLAIMS).toString()))) { + Serializable claimExtension = request.getExtensions().get(CLAIMS); + if (claimExtension == null || !claimRequest.equals(parseClaimRequest(claimExtension.toString()))) { logger.info("Mismatch between request object and regular parameter for claims, using request object"); } // we save the string because the object might not be a Java Serializable, and we can parse it easily enough anyway diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java index 81def22be6..cebcdd169f 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/request/ConnectRequestParameters.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -38,11 +37,18 @@ public interface ConnectRequestParameters { public String PROMPT_SEPARATOR = " "; // extensions - public String CSRF = "csrf"; public String APPROVED_SITE = "approved_site"; // responses public String ERROR = "error"; public String LOGIN_REQUIRED = "login_required"; + // audience + public String AUD = "aud"; + + // PKCE + public String CODE_CHALLENGE = "code_challenge"; + public String CODE_CHALLENGE_METHOD = "code_challenge_method"; + public String CODE_VERIFIER = "code_verifier"; + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java index f7d0623b92..05ddd5c0c3 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultApprovedSiteService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,6 +19,7 @@ import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Set; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; @@ -38,7 +40,7 @@ /** * Implementation of the ApprovedSiteService - * + * * @author Michael Joseph Walsh, aanganes * */ @@ -65,7 +67,7 @@ public Collection getAll() { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public ApprovedSite save(ApprovedSite approvedSite) { ApprovedSite a = approvedSiteRepository.save(approvedSite); statsService.resetCache(); @@ -78,11 +80,11 @@ public ApprovedSite getById(Long id) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(ApprovedSite approvedSite) { //Remove any associated access and refresh tokens - Set accessTokens = approvedSite.getApprovedAccessTokens(); + List accessTokens = getApprovedAccessTokens(approvedSite); for (OAuth2AccessTokenEntity token : accessTokens) { if (token.getRefreshToken() != null) { @@ -97,7 +99,7 @@ public void remove(ApprovedSite approvedSite) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public ApprovedSite createApprovedSite(String clientId, String userId, Date timeoutDate, Set allowedScopes) { ApprovedSite as = approvedSiteRepository.save(new ApprovedSite()); @@ -155,10 +157,12 @@ public void clearApprovedSitesForClient(ClientDetails client) { @Override public void clearExpiredSites() { - logger.info("Clearing expired approved sites"); + logger.debug("Clearing expired approved sites"); Collection expiredSites = getExpired(); - logger.info("Found " + expiredSites.size() + " expired approved sites."); + if (expiredSites.size() > 0) { + logger.info("Found " + expiredSites.size() + " expired approved sites."); + } if (expiredSites != null) { for (ApprovedSite expired : expiredSites) { remove(expired); @@ -178,4 +182,11 @@ private Collection getExpired() { return Collections2.filter(approvedSiteRepository.getAll(), isExpired); } + @Override + public List getApprovedAccessTokens( + ApprovedSite approvedSite) { + return tokenRepository.getAccessTokensForApprovedSite(approvedSite); + + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java index f88bfd7de5..65652b6dc7 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultBlacklistedSiteService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service.impl; @@ -35,7 +36,7 @@ * */ @Service -@Transactional +@Transactional(value="defaultTransactionManager") public class DefaultBlacklistedSiteService implements BlacklistedSiteService { @Autowired diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java index 8583827d53..00863745a3 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,9 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mitre.openid.connect.request.ConnectRequestParameters.MAX_AGE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.NONCE; + import java.util.Date; import java.util.Map; import java.util.Set; @@ -50,6 +54,7 @@ import com.google.common.collect.Sets; import com.nimbusds.jose.Algorithm; import com.nimbusds.jose.JWEHeader; +import com.nimbusds.jose.JWEObject; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; import com.nimbusds.jose.util.Base64URL; @@ -58,10 +63,9 @@ import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.PlainJWT; import com.nimbusds.jwt.SignedJWT; - /** * Default implementation of service to create specialty OpenID Connect tokens. - * + * * @author Amanda Anganes * */ @@ -92,7 +96,7 @@ public class DefaultOIDCTokenService implements OIDCTokenService { private OAuth2TokenEntityService tokenService; @Override - public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken) { + public JWT createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, OAuth2AccessTokenEntity accessToken) { JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); @@ -101,41 +105,42 @@ public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2R } - OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity(); - JWTClaimsSet idClaims = new JWTClaimsSet(); + JWT idToken = null; + + JWTClaimsSet.Builder idClaims = new JWTClaimsSet.Builder(); // if the auth time claim was explicitly requested OR if the client always wants the auth time, put it in - if (request.getExtensions().containsKey("max_age") + if (request.getExtensions().containsKey(MAX_AGE) || (request.getExtensions().containsKey("idtoken")) // TODO: parse the ID Token claims (#473) -- for now assume it could be in there || (client.getRequireAuthTime() != null && client.getRequireAuthTime())) { if (request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP) != null) { - + Long authTimestamp = Long.parseLong((String) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP)); if (authTimestamp != null) { - idClaims.setClaim("auth_time", authTimestamp / 1000L); + idClaims.claim("auth_time", authTimestamp / 1000L); } } else { // we couldn't find the timestamp! - logger.warn("Unable to find authentication timestamp! There is likely something wrong witht he configuration."); + logger.warn("Unable to find authentication timestamp! There is likely something wrong with the configuration."); } } - idClaims.setIssueTime(issueTime); + idClaims.issueTime(issueTime); if (client.getIdTokenValiditySeconds() != null) { Date expiration = new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); - idClaims.setExpirationTime(expiration); - idTokenEntity.setExpiration(expiration); + idClaims.expirationTime(expiration); } - idClaims.setIssuer(configBean.getIssuer()); - idClaims.setSubject(sub); - idClaims.setAudience(Lists.newArrayList(client.getClientId())); + idClaims.issuer(configBean.getIssuer()); + idClaims.subject(sub); + idClaims.audience(Lists.newArrayList(client.getClientId())); + idClaims.jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it - String nonce = (String)request.getExtensions().get("nonce"); + String nonce = (String)request.getExtensions().get(NONCE); if (!Strings.isNullOrEmpty(nonce)) { - idClaims.setCustomClaim("nonce", nonce); + idClaims.claim("nonce", nonce); } Set responseTypes = request.getResponseTypes(); @@ -143,9 +148,11 @@ public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2R if (responseTypes.contains("token")) { // calculate the token hash Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken); - idClaims.setClaim("at_hash", at_hash); + idClaims.claim("at_hash", at_hash); } + addCustomIdTokenClaims(idClaims, client, request, sub, accessToken); + if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) && client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) { @@ -154,11 +161,9 @@ public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2R if (encrypter != null) { - EncryptedJWT idToken = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), idClaims); - - encrypter.encryptJwt(idToken); + idToken = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), idClaims.build()); - idTokenEntity.setJwt(idToken); + encrypter.encryptJwt((JWEObject) idToken); } else { logger.error("Couldn't find encrypter for client: " + client.getClientId()); @@ -166,11 +171,9 @@ public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2R } else { - JWT idToken; - if (signingAlg.equals(Algorithm.NONE)) { // unsigned ID token - idToken = new PlainJWT(idClaims); + idToken = new PlainJWT(idClaims.build()); } else { @@ -180,40 +183,32 @@ public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2R || signingAlg.equals(JWSAlgorithm.HS384) || signingAlg.equals(JWSAlgorithm.HS512)) { - idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims); + JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, + jwtService.getDefaultSignerKeyId(), + null, null); + idToken = new SignedJWT(header, idClaims.build()); JWTSigningAndValidationService signer = symmetricCacheService.getSymmetricValidtor(client); // sign it with the client's secret signer.signJwt((SignedJWT) idToken); } else { - idClaims.setCustomClaim("kid", jwtService.getDefaultSignerKeyId()); + idClaims.claim("kid", jwtService.getDefaultSignerKeyId()); JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, jwtService.getDefaultSignerKeyId(), null, null); - - idToken = new SignedJWT(header, idClaims); + + idToken = new SignedJWT(header, idClaims.build()); // sign it with the server's key jwtService.signJwt((SignedJWT) idToken); } } - - idTokenEntity.setJwt(idToken); } - idTokenEntity.setAuthenticationHolder(accessToken.getAuthenticationHolder()); - - // create a scope set with just the special "id-token" scope - //Set idScopes = new HashSet(token.getScope()); // this would copy the original token's scopes in, we don't really want that - Set idScopes = Sets.newHashSet(SystemScopeService.ID_TOKEN_SCOPE); - idTokenEntity.setScope(idScopes); - - idTokenEntity.setClient(accessToken.getClient()); - - return idTokenEntity; + return idToken; } /** @@ -278,16 +273,19 @@ private OAuth2AccessTokenEntity createAssociatedToken(ClientDetailsEntity client authHolder = authenticationHolderRepository.save(authHolder); token.setAuthenticationHolder(authHolder); - JWTClaimsSet claims = new JWTClaimsSet(); - - claims.setAudience(Lists.newArrayList(client.getClientId())); - claims.setIssuer(configBean.getIssuer()); - claims.setIssueTime(new Date()); - claims.setExpirationTime(token.getExpiration()); - claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it + JWTClaimsSet claims = new JWTClaimsSet.Builder() + .audience(Lists.newArrayList(client.getClientId())) + .issuer(configBean.getIssuer()) + .issueTime(new Date()) + .expirationTime(token.getExpiration()) + .jwtID(UUID.randomUUID().toString()) // set a random NONCE in the middle of it + .build(); JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); - SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims); + JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, + jwtService.getDefaultSignerKeyId(), + null, null); + SignedJWT signed = new SignedJWT(header, claims); jwtService.signJwt(signed); @@ -339,4 +337,18 @@ public void setAuthenticationHolderRepository( this.authenticationHolderRepository = authenticationHolderRepository; } + /** + * Hook for subclasses that allows adding custom claims to the JWT + * that will be used as id token. + * @param idClaims the builder holding the current claims + * @param client information about the requesting client + * @param request request that caused the id token to be created + * @param sub subject auf the id token + * @param accessToken the access token + * @param authentication current authentication + */ + protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request, + String sub, OAuth2AccessTokenEntity accessToken) { + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java index ff57ad5e06..5c0637a5e2 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultScopeClaimTranslationService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,19 +18,17 @@ package org.mitre.openid.connect.service.impl; import java.util.HashSet; -import java.util.Map; import java.util.Set; import org.mitre.openid.connect.service.ScopeClaimTranslationService; import org.springframework.stereotype.Service; import com.google.common.collect.HashMultimap; -import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; /** * Service to map scopes to claims, and claims to Java field names - * + * * @author Amanda Anganes * */ @@ -37,13 +36,11 @@ public class DefaultScopeClaimTranslationService implements ScopeClaimTranslationService { private SetMultimap scopesToClaims = HashMultimap.create(); - private Map claimsToFields = Maps.newHashMap(); /** * Default constructor; initializes scopesToClaims map */ public DefaultScopeClaimTranslationService() { - scopesToClaims.put("openid", "sub"); scopesToClaims.put("profile", "name"); @@ -56,9 +53,9 @@ public DefaultScopeClaimTranslationService() { scopesToClaims.put("profile", "picture"); scopesToClaims.put("profile", "website"); scopesToClaims.put("profile", "gender"); - scopesToClaims.put("profile", "zone_info"); + scopesToClaims.put("profile", "zoneinfo"); scopesToClaims.put("profile", "locale"); - scopesToClaims.put("profile", "updated_time"); + scopesToClaims.put("profile", "updated_at"); scopesToClaims.put("profile", "birthdate"); scopesToClaims.put("email", "email"); @@ -68,32 +65,6 @@ public DefaultScopeClaimTranslationService() { scopesToClaims.put("phone", "phone_number_verified"); scopesToClaims.put("address", "address"); - - claimsToFields.put("sub", "sub"); - - claimsToFields.put("name", "name"); - claimsToFields.put("preferred_username", "preferredUsername"); - claimsToFields.put("given_name", "givenName"); - claimsToFields.put("family_name", "familyName"); - claimsToFields.put("middle_name", "middleName"); - claimsToFields.put("nickname", "nickname"); - claimsToFields.put("profile", "profile"); - claimsToFields.put("picture", "picture"); - claimsToFields.put("website", "website"); - claimsToFields.put("gender", "gender"); - claimsToFields.put("zone_info", "zoneinfo"); - claimsToFields.put("locale", "locale"); - claimsToFields.put("updated_time", "updatedTime"); - claimsToFields.put("birthdate", "birthdate"); - - claimsToFields.put("email", "email"); - claimsToFields.put("email_verified", "emailVerified"); - - claimsToFields.put("phone_number", "phoneNumber"); - claimsToFields.put("phone_number_verified", "phoneNumberVerified"); - - claimsToFields.put("address", "address"); - } /* (non-Javadoc) @@ -120,12 +91,4 @@ public Set getClaimsForScopeSet(Set scopes) { return result; } - /* (non-Javadoc) - * @see org.mitre.openid.connect.service.ScopeClaimTranslationService#getFieldNameForClaim(java.lang.String) - */ - @Override - public String getFieldNameForClaim(String claim) { - return claimsToFields.get(claim); - } - } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultStatsService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultStatsService.java index 389e143d1a..2305fc16ea 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultStatsService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultStatsService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service.impl; @@ -26,9 +27,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.openid.connect.model.ApprovedSite; +import org.mitre.openid.connect.model.ClientStat; import org.mitre.openid.connect.service.ApprovedSiteService; import org.mitre.openid.connect.service.StatsService; import org.springframework.beans.factory.annotation.Autowired; @@ -36,8 +36,6 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; /** * @author jricher @@ -49,9 +47,6 @@ public class DefaultStatsService implements StatsService { @Autowired private ApprovedSiteService approvedSiteService; - @Autowired - private ClientDetailsEntityService clientService; - // stats cache private Supplier> summaryCache = createSummaryCache(); @@ -65,18 +60,6 @@ public Map get() { }, 10, TimeUnit.MINUTES); } - private Supplier> byClientIdCache = createByClientIdCache(); - - private Supplier> createByClientIdCache() { - return Suppliers.memoizeWithExpiration(new Supplier>() { - @Override - public Map get() { - return computeByClientId(); - } - - }, 10, TimeUnit.MINUTES); - } - @Override public Map getSummaryStats() { return summaryCache.get(); @@ -103,55 +86,18 @@ private Map computeSummaryStats() { return e; } - /* (non-Javadoc) - * @see org.mitre.openid.connect.service.StatsService#calculateByClientId() - */ - @Override - public Map getByClientId() { - return byClientIdCache.get(); - } - - private Map computeByClientId() { - // get all approved sites - Collection allSites = approvedSiteService.getAll(); - - Multiset clientIds = HashMultiset.create(); - for (ApprovedSite approvedSite : allSites) { - clientIds.add(approvedSite.getClientId()); - } - - Map counts = getEmptyClientCountMap(); - for (String clientId : clientIds) { - ClientDetailsEntity client = clientService.loadClientByClientId(clientId); - counts.put(client.getId(), clientIds.count(clientId)); - } - - return counts; - } - /* (non-Javadoc) * @see org.mitre.openid.connect.service.StatsService#countForClientId(java.lang.String) */ @Override - public Integer getCountForClientId(Long id) { - - Map counts = getByClientId(); - return counts.get(id); + public ClientStat getCountForClientId(String clientId) { - } + Collection approvedSites = approvedSiteService.getByClientId(clientId); - /** - * Create a new map of all client ids set to zero - * @return - */ - private Map getEmptyClientCountMap() { - Map counts = new HashMap<>(); - Collection clients = clientService.getAllClients(); - for (ClientDetailsEntity client : clients) { - counts.put(client.getId(), 0); - } + ClientStat stat = new ClientStat(); + stat.setApprovedSiteCount(approvedSites.size()); - return counts; + return stat; } /** @@ -160,7 +106,6 @@ private Map getEmptyClientCountMap() { @Override public void resetCache() { summaryCache = createSummaryCache(); - byClientIdCache = createByClientIdCache(); } } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java index 463e9c0949..ce830d6492 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultUserInfoService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -28,9 +29,9 @@ /** * Implementation of the UserInfoService - * + * * @author Michael Joseph Walsh, jricher - * + * */ @Service public class DefaultUserInfoService implements UserInfoService { diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java index 86c141c8fd..3c530b5135 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultWhitelistedSiteService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -27,12 +28,12 @@ /** * Implementation of the WhitelistedSiteService - * + * * @author Michael Joseph Walsh, aanganes - * + * */ @Service -@Transactional +@Transactional(value="defaultTransactionManager") public class DefaultWhitelistedSiteService implements WhitelistedSiteService { @Autowired diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java index 4ab6f8ff67..ad60243c10 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DummyResourceSetService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -26,9 +25,9 @@ import org.springframework.stereotype.Service; /** - * Dummy resource set service that doesn't do anything; acts as a stub for the + * Dummy resource set service that doesn't do anything; acts as a stub for the * introspection service when the UMA functionality is disabled. - * + * * @author jricher * */ diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/InMemoryClientLogoLoadingService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/InMemoryClientLogoLoadingService.java new file mode 100644 index 0000000000..e16d0692a4 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/InMemoryClientLogoLoadingService.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * 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.impl; + +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClientBuilder; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.openid.connect.model.CachedImage; +import org.mitre.openid.connect.service.ClientLogoLoadingService; +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; + +/** + * @author jricher + * + */ +@Service("inMemoryClientLogoLoadingService") +public class InMemoryClientLogoLoadingService implements ClientLogoLoadingService { + + private LoadingCache cache; + + public InMemoryClientLogoLoadingService() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + /** + * + */ + public InMemoryClientLogoLoadingService(HttpClient httpClient) { + + cache = CacheBuilder.newBuilder() + .maximumSize(100) + .expireAfterAccess(14, TimeUnit.DAYS) + .build(new ClientLogoFetcher(httpClient)); + + } + + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.ClientLogoLoadingService#getLogo(org.mitre.oauth2.model.ClientDetailsEntity) + */ + @Override + public CachedImage getLogo(ClientDetailsEntity client) { + try { + if (client != null && !Strings.isNullOrEmpty(client.getLogoUri())) { + return cache.get(client); + } else { + return null; + } + } catch (UncheckedExecutionException | ExecutionException e) { + return null; + } + } + + /** + * @author jricher + * + */ + public class ClientLogoFetcher extends CacheLoader { + private HttpClient httpClient; + + public ClientLogoFetcher() { + this(HttpClientBuilder.create().useSystemProperties().build()); + } + + public ClientLogoFetcher(HttpClient httpClient) { + this.httpClient = httpClient; + } + + /* (non-Javadoc) + * @see com.google.common.cache.CacheLoader#load(java.lang.Object) + */ + @Override + public CachedImage load(ClientDetailsEntity key) throws Exception { + try { + HttpResponse response = httpClient.execute(new HttpGet(key.getLogoUri())); + + HttpEntity entity = response.getEntity(); + + CachedImage image = new CachedImage(); + + image.setContentType(entity.getContentType().getValue()); + image.setLength(entity.getContentLength()); + image.setData(IOUtils.toByteArray(entity.getContent())); + + return image; + } catch (IOException e) { + throw new IllegalArgumentException("Unable to load client image."); + } + } + + } + + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataServiceSupport.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataServiceSupport.java index a3905e66be..c4e787c9eb 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataServiceSupport.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataServiceSupport.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -36,7 +35,7 @@ public MITREidDataServiceSupport() { dateFormatter = new DateFormatter(); dateFormatter.setIso(ISO.DATE_TIME); } - + protected Date utcToDate(String value) { if (value == null) { return null; @@ -48,7 +47,7 @@ protected Date utcToDate(String value) { } return null; } - + protected String toUTCString(Date value) { if (value == null) { return null; diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_0.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_0.java index b34c9d031c..57c3257726 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_0.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_0.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,13 +17,18 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mitre.util.JsonUtils.readMap; +import static org.mitre.util.JsonUtils.readSet; + import java.io.IOException; import java.text.ParseException; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -46,6 +52,8 @@ import org.mitre.openid.connect.repository.BlacklistedSiteRepository; import org.mitre.openid.connect.repository.WhitelistedSiteRepository; import org.mitre.openid.connect.service.MITREidDataService; +import org.mitre.openid.connect.service.MITREidDataServiceExtension; +import org.mitre.openid.connect.service.MITREidDataServiceMaps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -64,9 +72,6 @@ import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jwt.JWTParser; - -import static org.mitre.util.JsonUtils.readMap; -import static org.mitre.util.JsonUtils.readSet; /** * * Data service to import MITREid 1.0 configuration. @@ -96,7 +101,18 @@ public class MITREidDataService_1_0 extends MITREidDataServiceSupport implements private OAuth2TokenRepository tokenRepository; @Autowired private SystemScopeRepository sysScopeRepository; - + @Autowired(required = false) + private List extensions = Collections.emptyList(); + + private MITREidDataServiceMaps maps = new MITREidDataServiceMaps(); + + private static final String THIS_VERSION = MITREID_CONNECT_1_0; + + @Override + public boolean supportsVersion(String version) { + return THIS_VERSION.equals(version); + } + /* (non-Javadoc) * @see org.mitre.openid.connect.service.MITREidDataService#export(com.google.gson.stream.JsonWriter) */ @@ -120,45 +136,56 @@ public void importData(JsonReader reader) throws IOException { while (reader.hasNext()) { JsonToken tok = reader.peek(); switch (tok) { - case NAME: - String name = reader.nextName(); - // find out which member it is - if (name.equals(CLIENTS)) { - readClients(reader); - } else if (name.equals(GRANTS)) { - readGrants(reader); - } else if (name.equals(WHITELISTEDSITES)) { - readWhitelistedSites(reader); - } else if (name.equals(BLACKLISTEDSITES)) { - readBlacklistedSites(reader); - } else if (name.equals(AUTHENTICATIONHOLDERS)) { - readAuthenticationHolders(reader); - } else if (name.equals(ACCESSTOKENS)) { - readAccessTokens(reader); - } else if (name.equals(REFRESHTOKENS)) { - readRefreshTokens(reader); - } else if (name.equals(SYSTEMSCOPES)) { - readSystemScopes(reader); - } else { - // unknown token, skip it + case NAME: + String name = reader.nextName(); + // find out which member it is + if (name.equals(CLIENTS)) { + readClients(reader); + } else if (name.equals(GRANTS)) { + readGrants(reader); + } else if (name.equals(WHITELISTEDSITES)) { + readWhitelistedSites(reader); + } else if (name.equals(BLACKLISTEDSITES)) { + readBlacklistedSites(reader); + } else if (name.equals(AUTHENTICATIONHOLDERS)) { + readAuthenticationHolders(reader); + } else if (name.equals(ACCESSTOKENS)) { + readAccessTokens(reader); + } else if (name.equals(REFRESHTOKENS)) { + readRefreshTokens(reader); + } else if (name.equals(SYSTEMSCOPES)) { + readSystemScopes(reader); + } else { + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.importExtensionData(name, reader); + break; + } + } + } + // unknown token, skip it + reader.skipValue(); + } + break; + case END_OBJECT: + // the object ended, we're done here + reader.endObject(); + continue; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - case END_OBJECT: - // the object ended, we're done here - reader.endObject(); - continue; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; } + continue; } } fixObjectReferences(); + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.fixExtensionObjectReferences(maps); + break; + } + } + maps.clearAll(); } - private Map refreshTokenToClientRefs = new HashMap<>(); - private Map refreshTokenToAuthHolderRefs = new HashMap<>(); - private Map refreshTokenOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -177,55 +204,49 @@ private void readRefreshTokens(JsonReader reader) throws IOException { Long authHolderId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("expiration")) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals("value")) { - String value = reader.nextString(); - try { - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("expiration")) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals("value")) { + String value = reader.nextString(); + try { + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals("clientId")) { + clientId = reader.nextString(); + } else if (name.equals("authenticationHolderId")) { + authHolderId = reader.nextLong(); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - } else if (name.equals("clientId")) { - clientId = reader.nextString(); - } else if (name.equals("authenticationHolderId")) { - authHolderId = reader.nextLong(); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = tokenRepository.saveRefreshToken(token).getId(); - refreshTokenToClientRefs.put(currentId, clientId); - refreshTokenToAuthHolderRefs.put(currentId, authHolderId); - refreshTokenOldToNewIdMap.put(currentId, newId); + maps.getRefreshTokenToClientRefs().put(currentId, clientId); + maps.getRefreshTokenToAuthHolderRefs().put(currentId, authHolderId); + maps.getRefreshTokenOldToNewIdMap().put(currentId, newId); logger.debug("Read refresh token {}", currentId); } reader.endArray(); logger.info("Done reading refresh tokens"); } - private Map accessTokenToClientRefs = new HashMap<>(); - private Map accessTokenToAuthHolderRefs = new HashMap<>(); - private Map accessTokenToRefreshTokenRefs = new HashMap<>(); - private Map accessTokenToIdTokenRefs = new HashMap<>(); - private Map accessTokenOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -243,70 +264,62 @@ private void readAccessTokens(JsonReader reader) throws IOException { String clientId = null; Long authHolderId = null; Long refreshTokenId = null; - Long idTokenId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("expiration")) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals("value")) { - String value = reader.nextString(); - try { - // all tokens are JWTs - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("expiration")) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals("value")) { + String value = reader.nextString(); + try { + // all tokens are JWTs + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals("clientId")) { + clientId = reader.nextString(); + } else if (name.equals("authenticationHolderId")) { + authHolderId = reader.nextLong(); + } else if (name.equals("refreshTokenId")) { + refreshTokenId = reader.nextLong(); + } else if (name.equals("scope")) { + Set scope = readSet(reader); + token.setScope(scope); + } else if (name.equals("type")) { + token.setTokenType(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - } else if (name.equals("clientId")) { - clientId = reader.nextString(); - } else if (name.equals("authenticationHolderId")) { - authHolderId = reader.nextLong(); - } else if (name.equals("refreshTokenId")) { - refreshTokenId = reader.nextLong(); - } else if (name.equals("idTokenId")) { - idTokenId = reader.nextLong(); - } else if (name.equals("scope")) { - Set scope = readSet(reader); - token.setScope(scope); - } else if (name.equals("type")) { - token.setTokenType(reader.nextString()); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = tokenRepository.saveAccessToken(token).getId(); - accessTokenToClientRefs.put(currentId, clientId); - accessTokenToAuthHolderRefs.put(currentId, authHolderId); + maps.getAccessTokenToClientRefs().put(currentId, clientId); + maps.getAccessTokenToAuthHolderRefs().put(currentId, authHolderId); if (refreshTokenId != null) { - accessTokenToRefreshTokenRefs.put(currentId, refreshTokenId); - } - if (idTokenId != null) { - accessTokenToIdTokenRefs.put(currentId, idTokenId); + maps.getAccessTokenToRefreshTokenRefs().put(currentId, refreshTokenId); } - accessTokenOldToNewIdMap.put(currentId, newId); + maps.getAccessTokenOldToNewIdMap().put(currentId, newId); logger.debug("Read access token {}", currentId); } reader.endArray(); logger.info("Done reading access tokens"); } - private Map authHolderOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -319,66 +332,66 @@ private void readAuthenticationHolders(JsonReader reader) throws IOException { Long currentId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("ownerId")) { - //not needed - reader.skipValue(); - } else if (name.equals("authentication")) { - OAuth2Request clientAuthorization = null; - Authentication userAuthentication = null; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String subName = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (subName.equals("clientAuthorization")) { - clientAuthorization = readAuthorizationRequest(reader); - } else if (subName.equals("userAuthentication")) { - // skip binary encoded version - reader.skipValue(); - - } else if (subName.equals("savedUserAuthentication")) { - userAuthentication = readSavedUserAuthentication(reader); - - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("ownerId")) { + //not needed + reader.skipValue(); + } else if (name.equals("authentication")) { + OAuth2Request clientAuthorization = null; + Authentication userAuthentication = null; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String subName = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (subName.equals("clientAuthorization")) { + clientAuthorization = readAuthorizationRequest(reader); + } else if (subName.equals("userAuthentication")) { + // skip binary encoded version + reader.skipValue(); + + } else if (subName.equals("savedUserAuthentication")) { + userAuthentication = readSavedUserAuthentication(reader); + + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; } + reader.endObject(); + OAuth2Authentication auth = new OAuth2Authentication(clientAuthorization, userAuthentication); + ahe.setAuthentication(auth); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - reader.endObject(); - OAuth2Authentication auth = new OAuth2Authentication(clientAuthorization, userAuthentication); - ahe.setAuthentication(auth); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = authHolderRepository.save(ahe).getId(); - authHolderOldToNewIdMap.put(currentId, newId); + maps.getAuthHolderOldToNewIdMap().put(currentId, newId); logger.debug("Read authentication holder {}", currentId); } reader.endArray(); @@ -398,103 +411,100 @@ private OAuth2Request readAuthorizationRequest(JsonReader reader) throws IOExcep reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("authorizationParameters")) { - authorizationParameters = readMap(reader); - } else if (name.equals("approvalParameters")) { - reader.skipValue(); - } else if (name.equals("clientId")) { - clientId = reader.nextString(); - } else if (name.equals("scope")) { - scope = readSet(reader); - } else if (name.equals("resourceIds")) { - resourceIds = readSet(reader); - } else if (name.equals("authorities")) { - Set authorityStrs = readSet(reader); - authorities = new HashSet<>(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); - } - } else if (name.equals("approved")) { - approved = reader.nextBoolean(); - } else if (name.equals("denied")) { - if (approved == false) { - approved = !reader.nextBoolean(); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("authorizationParameters")) { + authorizationParameters = readMap(reader); + } else if (name.equals("approvalParameters")) { + reader.skipValue(); + } else if (name.equals("clientId")) { + clientId = reader.nextString(); + } else if (name.equals("scope")) { + scope = readSet(reader); + } else if (name.equals("resourceIds")) { + resourceIds = readSet(reader); + } else if (name.equals("authorities")) { + Set authorityStrs = readSet(reader); + authorities = new HashSet<>(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + } else if (name.equals("approved")) { + approved = reader.nextBoolean(); + } else if (name.equals("denied")) { + if (approved == false) { + approved = !reader.nextBoolean(); + } + } else if (name.equals("redirectUri")) { + redirectUri = reader.nextString(); + } else if (name.equals("responseTypes")) { + responseTypes = readSet(reader); + } else { + reader.skipValue(); } - } else if (name.equals("redirectUri")) { - redirectUri = reader.nextString(); - } else if (name.equals("responseTypes")) { - responseTypes = readSet(reader); - } else { + break; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); return new OAuth2Request(authorizationParameters, clientId, authorities, approved, scope, resourceIds, redirectUri, responseTypes, null); } - + /** * @param reader * @return - * @throws IOException + * @throws IOException */ private SavedUserAuthentication readSavedUserAuthentication(JsonReader reader) throws IOException { SavedUserAuthentication savedUserAuth = new SavedUserAuthentication(); reader.beginObject(); - + while (reader.hasNext()) { switch(reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("name")) { - savedUserAuth.setName(reader.nextString()); - } else if (name.equals("sourceClass")) { - savedUserAuth.setSourceClass(reader.nextString()); - } else if (name.equals("authenticated")) { - savedUserAuth.setAuthenticated(reader.nextBoolean()); - } else if (name.equals("authorities")) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("name")) { + savedUserAuth.setName(reader.nextString()); + } else if (name.equals("sourceClass")) { + savedUserAuth.setSourceClass(reader.nextString()); + } else if (name.equals("authenticated")) { + savedUserAuth.setAuthenticated(reader.nextBoolean()); + } else if (name.equals("authorities")) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + savedUserAuth.setAuthorities(authorities); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - savedUserAuth.setAuthorities(authorities); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } - + reader.endObject(); return savedUserAuth; } - Map grantOldToNewIdMap = new HashMap<>(); - Map> grantToAccessTokensRefs = new HashMap<>(); - /** * @param reader * @throws IOException @@ -509,61 +519,59 @@ private void readGrants(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("accessDate")) { - Date date = utcToDate(reader.nextString()); - site.setAccessDate(date); - } else if (name.equals("clientId")) { - site.setClientId(reader.nextString()); - } else if (name.equals("creationDate")) { - Date date = utcToDate(reader.nextString()); - site.setCreationDate(date); - } else if (name.equals("timeoutDate")) { - Date date = utcToDate(reader.nextString()); - site.setTimeoutDate(date); - } else if (name.equals("userId")) { - site.setUserId(reader.nextString()); - } else if (name.equals("allowedScopes")) { - Set allowedScopes = readSet(reader); - site.setAllowedScopes(allowedScopes); - } else if (name.equals("whitelistedSiteId")) { - whitelistedSiteId = reader.nextLong(); - } else if (name.equals("approvedAccessTokens")) { - tokenIds = readSet(reader); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("accessDate")) { + Date date = utcToDate(reader.nextString()); + site.setAccessDate(date); + } else if (name.equals("clientId")) { + site.setClientId(reader.nextString()); + } else if (name.equals("creationDate")) { + Date date = utcToDate(reader.nextString()); + site.setCreationDate(date); + } else if (name.equals("timeoutDate")) { + Date date = utcToDate(reader.nextString()); + site.setTimeoutDate(date); + } else if (name.equals("userId")) { + site.setUserId(reader.nextString()); + } else if (name.equals("allowedScopes")) { + Set allowedScopes = readSet(reader); + site.setAllowedScopes(allowedScopes); + } else if (name.equals("whitelistedSiteId")) { + whitelistedSiteId = reader.nextLong(); + } else if (name.equals("approvedAccessTokens")) { + tokenIds = readSet(reader); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = approvedSiteRepository.save(site).getId(); - grantOldToNewIdMap.put(currentId, newId); + maps.getGrantOldToNewIdMap().put(currentId, newId); if (whitelistedSiteId != null) { logger.debug("Ignoring whitelisted site marker on approved site."); } if (tokenIds != null) { - grantToAccessTokensRefs.put(currentId, tokenIds); + maps.getGrantToAccessTokensRefs().put(currentId, tokenIds); } logger.debug("Read grant {}", currentId); } reader.endArray(); logger.info("Done reading grants"); } - Map whitelistedSiteOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -576,33 +584,33 @@ private void readWhitelistedSites(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("clientId")) { - wlSite.setClientId(reader.nextString()); - } else if (name.equals("creatorUserId")) { - wlSite.setCreatorUserId(reader.nextString()); - } else if (name.equals("allowedScopes")) { - Set allowedScopes = readSet(reader); - wlSite.setAllowedScopes(allowedScopes); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("clientId")) { + wlSite.setClientId(reader.nextString()); + } else if (name.equals("creatorUserId")) { + wlSite.setCreatorUserId(reader.nextString()); + } else if (name.equals("allowedScopes")) { + Set allowedScopes = readSet(reader); + wlSite.setAllowedScopes(allowedScopes); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = wlSiteRepository.save(wlSite).getId(); - whitelistedSiteOldToNewIdMap.put(currentId, newId); + maps.getWhitelistedSiteOldToNewIdMap().put(currentId, newId); } reader.endArray(); logger.info("Done reading whitelisted sites"); @@ -619,23 +627,23 @@ private void readBlacklistedSites(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals("id")) { - reader.skipValue(); - } else if (name.equals("uri")) { - blSite.setUri(reader.nextString()); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals("id")) { + reader.skipValue(); + } else if (name.equals("uri")) { + blSite.setUri(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -656,125 +664,125 @@ private void readClients(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("clientId")) { - client.setClientId(reader.nextString()); - } else if (name.equals("resourceIds")) { - Set resourceIds = readSet(reader); - client.setResourceIds(resourceIds); - } else if (name.equals("secret")) { - client.setClientSecret(reader.nextString()); - } else if (name.equals("scope")) { - Set scope = readSet(reader); - client.setScope(scope); - } else if (name.equals("authorities")) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet<>(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("clientId")) { + client.setClientId(reader.nextString()); + } else if (name.equals("resourceIds")) { + Set resourceIds = readSet(reader); + client.setResourceIds(resourceIds); + } else if (name.equals("secret")) { + client.setClientSecret(reader.nextString()); + } else if (name.equals("scope")) { + Set scope = readSet(reader); + client.setScope(scope); + } else if (name.equals("authorities")) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet<>(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + client.setAuthorities(authorities); + } else if (name.equals("accessTokenValiditySeconds")) { + client.setAccessTokenValiditySeconds(reader.nextInt()); + } else if (name.equals("refreshTokenValiditySeconds")) { + client.setRefreshTokenValiditySeconds(reader.nextInt()); + } else if (name.equals("redirectUris")) { + Set redirectUris = readSet(reader); + client.setRedirectUris(redirectUris); + } else if (name.equals("name")) { + client.setClientName(reader.nextString()); + } else if (name.equals("uri")) { + client.setClientUri(reader.nextString()); + } else if (name.equals("logoUri")) { + client.setLogoUri(reader.nextString()); + } else if (name.equals("contacts")) { + Set contacts = readSet(reader); + client.setContacts(contacts); + } else if (name.equals("tosUri")) { + client.setTosUri(reader.nextString()); + } else if (name.equals("tokenEndpointAuthMethod")) { + AuthMethod am = AuthMethod.getByValue(reader.nextString()); + client.setTokenEndpointAuthMethod(am); + } else if (name.equals("grantTypes")) { + Set grantTypes = readSet(reader); + client.setGrantTypes(grantTypes); + } else if (name.equals("responseTypes")) { + Set responseTypes = readSet(reader); + client.setResponseTypes(responseTypes); + } else if (name.equals("policyUri")) { + client.setPolicyUri(reader.nextString()); + } else if (name.equals("applicationType")) { + AppType appType = AppType.getByValue(reader.nextString()); + client.setApplicationType(appType); + } else if (name.equals("sectorIdentifierUri")) { + client.setSectorIdentifierUri(reader.nextString()); + } else if (name.equals("subjectType")) { + SubjectType st = SubjectType.getByValue(reader.nextString()); + client.setSubjectType(st); + } else if (name.equals("jwks_uri")) { + client.setJwksUri(reader.nextString()); + } else if (name.equals("requestObjectSigningAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setRequestObjectSigningAlg(alg); + } else if (name.equals("userInfoEncryptedResponseAlg")) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setUserInfoEncryptedResponseAlg(alg); + } else if (name.equals("userInfoEncryptedResponseEnc")) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setUserInfoEncryptedResponseEnc(alg); + } else if (name.equals("userInfoSignedResponseAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setUserInfoSignedResponseAlg(alg); + } else if (name.equals("idTokenSignedResonseAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setIdTokenSignedResponseAlg(alg); + } else if (name.equals("idTokenEncryptedResponseAlg")) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setIdTokenEncryptedResponseAlg(alg); + } else if (name.equals("idTokenEncryptedResponseEnc")) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setIdTokenEncryptedResponseEnc(alg); + } else if (name.equals("tokenEndpointAuthSigningAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setTokenEndpointAuthSigningAlg(alg); + } else if (name.equals("defaultMaxAge")) { + client.setDefaultMaxAge(reader.nextInt()); + } else if (name.equals("requireAuthTime")) { + client.setRequireAuthTime(reader.nextBoolean()); + } else if (name.equals("defaultACRValues")) { + Set defaultACRvalues = readSet(reader); + client.setDefaultACRvalues(defaultACRvalues); + } else if (name.equals("initiateLoginUri")) { + client.setInitiateLoginUri(reader.nextString()); + } else if (name.equals("postLogoutRedirectUri")) { + HashSet postLogoutUris = Sets.newHashSet(reader.nextString()); + client.setPostLogoutRedirectUris(postLogoutUris); + } else if (name.equals("requestUris")) { + Set requestUris = readSet(reader); + client.setRequestUris(requestUris); + } else if (name.equals("description")) { + client.setClientDescription(reader.nextString()); + } else if (name.equals("allowIntrospection")) { + client.setAllowIntrospection(reader.nextBoolean()); + } else if (name.equals("reuseRefreshToken")) { + client.setReuseRefreshToken(reader.nextBoolean()); + } else if (name.equals("dynamicallyRegistered")) { + client.setDynamicallyRegistered(reader.nextBoolean()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - client.setAuthorities(authorities); - } else if (name.equals("accessTokenValiditySeconds")) { - client.setAccessTokenValiditySeconds(reader.nextInt()); - } else if (name.equals("refreshTokenValiditySeconds")) { - client.setRefreshTokenValiditySeconds(reader.nextInt()); - } else if (name.equals("redirectUris")) { - Set redirectUris = readSet(reader); - client.setRedirectUris(redirectUris); - } else if (name.equals("name")) { - client.setClientName(reader.nextString()); - } else if (name.equals("uri")) { - client.setClientUri(reader.nextString()); - } else if (name.equals("logoUri")) { - client.setLogoUri(reader.nextString()); - } else if (name.equals("contacts")) { - Set contacts = readSet(reader); - client.setContacts(contacts); - } else if (name.equals("tosUri")) { - client.setTosUri(reader.nextString()); - } else if (name.equals("tokenEndpointAuthMethod")) { - AuthMethod am = AuthMethod.getByValue(reader.nextString()); - client.setTokenEndpointAuthMethod(am); - } else if (name.equals("grantTypes")) { - Set grantTypes = readSet(reader); - client.setGrantTypes(grantTypes); - } else if (name.equals("responseTypes")) { - Set responseTypes = readSet(reader); - client.setResponseTypes(responseTypes); - } else if (name.equals("policyUri")) { - client.setPolicyUri(reader.nextString()); - } else if (name.equals("applicationType")) { - AppType appType = AppType.getByValue(reader.nextString()); - client.setApplicationType(appType); - } else if (name.equals("sectorIdentifierUri")) { - client.setSectorIdentifierUri(reader.nextString()); - } else if (name.equals("subjectType")) { - SubjectType st = SubjectType.getByValue(reader.nextString()); - client.setSubjectType(st); - } else if (name.equals("jwks_uri")) { - client.setJwksUri(reader.nextString()); - } else if (name.equals("requestObjectSigningAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setRequestObjectSigningAlg(alg); - } else if (name.equals("userInfoEncryptedResponseAlg")) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setUserInfoEncryptedResponseAlg(alg); - } else if (name.equals("userInfoEncryptedResponseEnc")) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setUserInfoEncryptedResponseEnc(alg); - } else if (name.equals("userInfoSignedResponseAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setUserInfoSignedResponseAlg(alg); - } else if (name.equals("idTokenSignedResonseAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setIdTokenSignedResponseAlg(alg); - } else if (name.equals("idTokenEncryptedResponseAlg")) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setIdTokenEncryptedResponseAlg(alg); - } else if (name.equals("idTokenEncryptedResponseEnc")) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setIdTokenEncryptedResponseEnc(alg); - } else if (name.equals("tokenEndpointAuthSigningAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setTokenEndpointAuthSigningAlg(alg); - } else if (name.equals("defaultMaxAge")) { - client.setDefaultMaxAge(reader.nextInt()); - } else if (name.equals("requireAuthTime")) { - client.setRequireAuthTime(reader.nextBoolean()); - } else if (name.equals("defaultACRValues")) { - Set defaultACRvalues = readSet(reader); - client.setDefaultACRvalues(defaultACRvalues); - } else if (name.equals("initiateLoginUri")) { - client.setInitiateLoginUri(reader.nextString()); - } else if (name.equals("postLogoutRedirectUri")) { - HashSet postLogoutUris = Sets.newHashSet(reader.nextString()); - client.setPostLogoutRedirectUris(postLogoutUris); - } else if (name.equals("requestUris")) { - Set requestUris = readSet(reader); - client.setRequestUris(requestUris); - } else if (name.equals("description")) { - client.setClientDescription(reader.nextString()); - } else if (name.equals("allowIntrospection")) { - client.setAllowIntrospection(reader.nextBoolean()); - } else if (name.equals("reuseRefreshToken")) { - client.setReuseRefreshToken(reader.nextBoolean()); - } else if (name.equals("dynamicallyRegistered")) { - client.setDynamicallyRegistered(reader.nextBoolean()); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -798,32 +806,32 @@ private void readSystemScopes(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("value")) { - scope.setValue(reader.nextString()); - } else if (name.equals("description")) { - scope.setDescription(reader.nextString()); - } else if (name.equals("allowDynReg")) { - // previously "allowDynReg" scopes are now tagged as "not restricted" and vice versa - scope.setRestricted(!reader.nextBoolean()); - } else if (name.equals("defaultScope")) { - scope.setDefaultScope(reader.nextBoolean()); - } else if (name.equals("icon")) { - scope.setIcon(reader.nextString()); - } else { - logger.debug("found unexpected entry"); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("value")) { + scope.setValue(reader.nextString()); + } else if (name.equals("description")) { + scope.setDescription(reader.nextString()); + } else if (name.equals("allowDynReg")) { + // previously "allowDynReg" scopes are now tagged as "not restricted" and vice versa + scope.setRestricted(!reader.nextBoolean()); + } else if (name.equals("defaultScope")) { + scope.setDefaultScope(reader.nextBoolean()); + } else if (name.equals("icon")) { + scope.setIcon(reader.nextString()); + } else { + logger.debug("found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -834,80 +842,65 @@ private void readSystemScopes(JsonReader reader) throws IOException { } private void fixObjectReferences() { - for (Long oldRefreshTokenId : refreshTokenToClientRefs.keySet()) { - String clientRef = refreshTokenToClientRefs.get(oldRefreshTokenId); + for (Long oldRefreshTokenId : maps.getRefreshTokenToClientRefs().keySet()) { + String clientRef = maps.getRefreshTokenToClientRefs().get(oldRefreshTokenId); ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); refreshToken.setClient(client); tokenRepository.saveRefreshToken(refreshToken); } - refreshTokenToClientRefs.clear(); - for (Long oldRefreshTokenId : refreshTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = refreshTokenToAuthHolderRefs.get(oldRefreshTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); + for (Long oldRefreshTokenId : maps.getRefreshTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getRefreshTokenToAuthHolderRefs().get(oldRefreshTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); refreshToken.setAuthenticationHolder(authHolder); tokenRepository.saveRefreshToken(refreshToken); } - refreshTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToClientRefs.keySet()) { - String clientRef = accessTokenToClientRefs.get(oldAccessTokenId); + for (Long oldAccessTokenId : maps.getAccessTokenToClientRefs().keySet()) { + String clientRef = maps.getAccessTokenToClientRefs().get(oldAccessTokenId); ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setClient(client); tokenRepository.saveAccessToken(accessToken); } - accessTokenToClientRefs.clear(); - for (Long oldAccessTokenId : accessTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = accessTokenToAuthHolderRefs.get(oldAccessTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); + for (Long oldAccessTokenId : maps.getAccessTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getAccessTokenToAuthHolderRefs().get(oldAccessTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setAuthenticationHolder(authHolder); tokenRepository.saveAccessToken(accessToken); } - accessTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToRefreshTokenRefs.keySet()) { - Long oldRefreshTokenId = accessTokenToRefreshTokenRefs.get(oldAccessTokenId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + maps.getAccessTokenToAuthHolderRefs().clear(); + for (Long oldAccessTokenId : maps.getAccessTokenToRefreshTokenRefs().keySet()) { + Long oldRefreshTokenId = maps.getAccessTokenToRefreshTokenRefs().get(oldAccessTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setRefreshToken(refreshToken); tokenRepository.saveAccessToken(accessToken); } - accessTokenToRefreshTokenRefs.clear(); - refreshTokenOldToNewIdMap.clear(); - for (Long oldAccessTokenId : accessTokenToIdTokenRefs.keySet()) { - Long oldIdTokenId = accessTokenToIdTokenRefs.get(oldAccessTokenId); - Long newIdTokenId = accessTokenOldToNewIdMap.get(oldIdTokenId); - OAuth2AccessTokenEntity idToken = tokenRepository.getAccessTokenById(newIdTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setIdToken(idToken); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToIdTokenRefs.clear(); - whitelistedSiteOldToNewIdMap.clear(); - for (Long oldGrantId : grantToAccessTokensRefs.keySet()) { - Set oldAccessTokenIds = grantToAccessTokensRefs.get(oldGrantId); - Set tokens = new HashSet<>(); + for (Long oldGrantId : maps.getGrantToAccessTokensRefs().keySet()) { + Set oldAccessTokenIds = maps.getGrantToAccessTokensRefs().get(oldGrantId); + + Long newGrantId = maps.getGrantOldToNewIdMap().get(oldGrantId); + ApprovedSite site = approvedSiteRepository.getById(newGrantId); + for(Long oldTokenId : oldAccessTokenIds) { - Long newTokenId = accessTokenOldToNewIdMap.get(oldTokenId); - tokens.add(tokenRepository.getAccessTokenById(newTokenId)); + Long newTokenId = maps.getAccessTokenOldToNewIdMap().get(oldTokenId); + OAuth2AccessTokenEntity token = tokenRepository.getAccessTokenById(newTokenId); + token.setApprovedSite(site); + tokenRepository.saveAccessToken(token); } - Long newGrantId = grantOldToNewIdMap.get(oldGrantId); - ApprovedSite site = approvedSiteRepository.getById(newGrantId); - site.setApprovedAccessTokens(tokens); + approvedSiteRepository.save(site); } - accessTokenOldToNewIdMap.clear(); - grantOldToNewIdMap.clear(); } } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_1.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_1.java index 3206b962d6..38287ee633 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_1.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_1.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,14 +17,19 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mitre.util.JsonUtils.readMap; +import static org.mitre.util.JsonUtils.readSet; + import java.io.IOException; import java.io.Serializable; import java.text.ParseException; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -48,6 +54,8 @@ import org.mitre.openid.connect.repository.BlacklistedSiteRepository; import org.mitre.openid.connect.repository.WhitelistedSiteRepository; import org.mitre.openid.connect.service.MITREidDataService; +import org.mitre.openid.connect.service.MITREidDataServiceExtension; +import org.mitre.openid.connect.service.MITREidDataServiceMaps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -67,9 +75,6 @@ import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jwt.JWTParser; -import static org.mitre.util.JsonUtils.readMap; -import static org.mitre.util.JsonUtils.readSet; - /** * * Data service to import MITREid 1.1 configuration. @@ -99,7 +104,18 @@ public class MITREidDataService_1_1 extends MITREidDataServiceSupport implements private OAuth2TokenRepository tokenRepository; @Autowired private SystemScopeRepository sysScopeRepository; - + @Autowired(required = false) + private List extensions = Collections.emptyList(); + + private static final String THIS_VERSION = MITREID_CONNECT_1_1; + + private MITREidDataServiceMaps maps = new MITREidDataServiceMaps(); + + @Override + public boolean supportsVersion(String version) { + return THIS_VERSION.equals(version); + } + /* (non-Javadoc) * @see org.mitre.openid.connect.service.MITREidDataService#export(com.google.gson.stream.JsonWriter) */ @@ -122,46 +138,57 @@ public void importData(JsonReader reader) throws IOException { while (reader.hasNext()) { JsonToken tok = reader.peek(); switch (tok) { - case NAME: - String name = reader.nextName(); - // find out which member it is - if (name.equals(CLIENTS)) { - readClients(reader); - } else if (name.equals(GRANTS)) { - readGrants(reader); - } else if (name.equals(WHITELISTEDSITES)) { - readWhitelistedSites(reader); - } else if (name.equals(BLACKLISTEDSITES)) { - readBlacklistedSites(reader); - } else if (name.equals(AUTHENTICATIONHOLDERS)) { - readAuthenticationHolders(reader); - } else if (name.equals(ACCESSTOKENS)) { - readAccessTokens(reader); - } else if (name.equals(REFRESHTOKENS)) { - readRefreshTokens(reader); - } else if (name.equals(SYSTEMSCOPES)) { - readSystemScopes(reader); - } else { - // unknown token, skip it + case NAME: + String name = reader.nextName(); + // find out which member it is + if (name.equals(CLIENTS)) { + readClients(reader); + } else if (name.equals(GRANTS)) { + readGrants(reader); + } else if (name.equals(WHITELISTEDSITES)) { + readWhitelistedSites(reader); + } else if (name.equals(BLACKLISTEDSITES)) { + readBlacklistedSites(reader); + } else if (name.equals(AUTHENTICATIONHOLDERS)) { + readAuthenticationHolders(reader); + } else if (name.equals(ACCESSTOKENS)) { + readAccessTokens(reader); + } else if (name.equals(REFRESHTOKENS)) { + readRefreshTokens(reader); + } else if (name.equals(SYSTEMSCOPES)) { + readSystemScopes(reader); + } else { + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.importExtensionData(name, reader); + break; + } + } + } + // unknown token, skip it + reader.skipValue(); + } + break; + case END_OBJECT: + // the object ended, we're done here + reader.endObject(); + continue; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - case END_OBJECT: - // the object ended, we're done here - reader.endObject(); - continue; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } fixObjectReferences(); + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.fixExtensionObjectReferences(maps); + break; + } + } + maps.clearAll(); } - private Map refreshTokenToClientRefs = new HashMap<>(); - private Map refreshTokenToAuthHolderRefs = new HashMap<>(); - private Map refreshTokenOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -180,55 +207,49 @@ private void readRefreshTokens(JsonReader reader) throws IOException { Long authHolderId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("expiration")) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals("value")) { - String value = reader.nextString(); - try { - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("expiration")) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals("value")) { + String value = reader.nextString(); + try { + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals("clientId")) { + clientId = reader.nextString(); + } else if (name.equals("authenticationHolderId")) { + authHolderId = reader.nextLong(); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - } else if (name.equals("clientId")) { - clientId = reader.nextString(); - } else if (name.equals("authenticationHolderId")) { - authHolderId = reader.nextLong(); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = tokenRepository.saveRefreshToken(token).getId(); - refreshTokenToClientRefs.put(currentId, clientId); - refreshTokenToAuthHolderRefs.put(currentId, authHolderId); - refreshTokenOldToNewIdMap.put(currentId, newId); + maps.getRefreshTokenToClientRefs().put(currentId, clientId); + maps.getRefreshTokenToAuthHolderRefs().put(currentId, authHolderId); + maps.getRefreshTokenOldToNewIdMap().put(currentId, newId); logger.debug("Read refresh token {}", currentId); } reader.endArray(); logger.info("Done reading refresh tokens"); } - private Map accessTokenToClientRefs = new HashMap<>(); - private Map accessTokenToAuthHolderRefs = new HashMap<>(); - private Map accessTokenToRefreshTokenRefs = new HashMap<>(); - private Map accessTokenToIdTokenRefs = new HashMap<>(); - private Map accessTokenOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -246,70 +267,62 @@ private void readAccessTokens(JsonReader reader) throws IOException { String clientId = null; Long authHolderId = null; Long refreshTokenId = null; - Long idTokenId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("expiration")) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals("value")) { - String value = reader.nextString(); - try { - // all tokens are JWTs - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("expiration")) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals("value")) { + String value = reader.nextString(); + try { + // all tokens are JWTs + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals("clientId")) { + clientId = reader.nextString(); + } else if (name.equals("authenticationHolderId")) { + authHolderId = reader.nextLong(); + } else if (name.equals("refreshTokenId")) { + refreshTokenId = reader.nextLong(); + } else if (name.equals("scope")) { + Set scope = readSet(reader); + token.setScope(scope); + } else if (name.equals("type")) { + token.setTokenType(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - } else if (name.equals("clientId")) { - clientId = reader.nextString(); - } else if (name.equals("authenticationHolderId")) { - authHolderId = reader.nextLong(); - } else if (name.equals("refreshTokenId")) { - refreshTokenId = reader.nextLong(); - } else if (name.equals("idTokenId")) { - idTokenId = reader.nextLong(); - } else if (name.equals("scope")) { - Set scope = readSet(reader); - token.setScope(scope); - } else if (name.equals("type")) { - token.setTokenType(reader.nextString()); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = tokenRepository.saveAccessToken(token).getId(); - accessTokenToClientRefs.put(currentId, clientId); - accessTokenToAuthHolderRefs.put(currentId, authHolderId); + maps.getAccessTokenToClientRefs().put(currentId, clientId); + maps.getAccessTokenToAuthHolderRefs().put(currentId, authHolderId); if (refreshTokenId != null) { - accessTokenToRefreshTokenRefs.put(currentId, refreshTokenId); + maps.getAccessTokenToRefreshTokenRefs().put(currentId, refreshTokenId); } - if (idTokenId != null) { - accessTokenToIdTokenRefs.put(currentId, idTokenId); - } - accessTokenOldToNewIdMap.put(currentId, newId); + maps.getAccessTokenOldToNewIdMap().put(currentId, newId); logger.debug("Read access token {}", currentId); } reader.endArray(); logger.info("Done reading access tokens"); } - private Map authHolderOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -322,66 +335,66 @@ private void readAuthenticationHolders(JsonReader reader) throws IOException { Long currentId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("ownerId")) { - //not needed - reader.skipValue(); - } else if (name.equals("authentication")) { - OAuth2Request clientAuthorization = null; - Authentication userAuthentication = null; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String subName = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); // skip null values - } else if (subName.equals("clientAuthorization")) { - clientAuthorization = readAuthorizationRequest(reader); - } else if (subName.equals("userAuthentication")) { - // skip binary encoded version - reader.skipValue(); - - } else if (subName.equals("savedUserAuthentication")) { - userAuthentication = readSavedUserAuthentication(reader); - - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("ownerId")) { + //not needed + reader.skipValue(); + } else if (name.equals("authentication")) { + OAuth2Request clientAuthorization = null; + Authentication userAuthentication = null; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String subName = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); // skip null values + } else if (subName.equals("clientAuthorization")) { + clientAuthorization = readAuthorizationRequest(reader); + } else if (subName.equals("userAuthentication")) { + // skip binary encoded version + reader.skipValue(); + + } else if (subName.equals("savedUserAuthentication")) { + userAuthentication = readSavedUserAuthentication(reader); + + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; } + reader.endObject(); + OAuth2Authentication auth = new OAuth2Authentication(clientAuthorization, userAuthentication); + ahe.setAuthentication(auth); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - reader.endObject(); - OAuth2Authentication auth = new OAuth2Authentication(clientAuthorization, userAuthentication); - ahe.setAuthentication(auth); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = authHolderRepository.save(ahe).getId(); - authHolderOldToNewIdMap.put(currentId, newId); + maps.getAuthHolderOldToNewIdMap().put(currentId, newId); logger.debug("Read authentication holder {}", currentId); } reader.endArray(); @@ -402,109 +415,106 @@ private OAuth2Request readAuthorizationRequest(JsonReader reader) throws IOExcep reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("requestParameters")) { - requestParameters = readMap(reader); - } else if (name.equals("clientId")) { - clientId = reader.nextString(); - } else if (name.equals("scope")) { - scope = readSet(reader); - } else if (name.equals("resourceIds")) { - resourceIds = readSet(reader); - } else if (name.equals("authorities")) { - Set authorityStrs = readSet(reader); - authorities = new HashSet<>(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); - } - } else if (name.equals("approved")) { - approved = reader.nextBoolean(); - } else if (name.equals("denied")) { - if (approved == false) { - approved = !reader.nextBoolean(); - } - } else if (name.equals("redirectUri")) { - redirectUri = reader.nextString(); - } else if (name.equals("responseTypes")) { - responseTypes = readSet(reader); - } else if (name.equals("extensions")) { - // skip the binary encoded version - reader.skipValue(); - } else if (name.equals("extensionStrings")) { - Map extEnc = readMap(reader); - for (Entry entry : extEnc.entrySet()) { - extensions.put(entry.getKey(), entry.getValue()); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("requestParameters")) { + requestParameters = readMap(reader); + } else if (name.equals("clientId")) { + clientId = reader.nextString(); + } else if (name.equals("scope")) { + scope = readSet(reader); + } else if (name.equals("resourceIds")) { + resourceIds = readSet(reader); + } else if (name.equals("authorities")) { + Set authorityStrs = readSet(reader); + authorities = new HashSet<>(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + } else if (name.equals("approved")) { + approved = reader.nextBoolean(); + } else if (name.equals("denied")) { + if (approved == false) { + approved = !reader.nextBoolean(); + } + } else if (name.equals("redirectUri")) { + redirectUri = reader.nextString(); + } else if (name.equals("responseTypes")) { + responseTypes = readSet(reader); + } else if (name.equals("extensions")) { + // skip the binary encoded version + reader.skipValue(); + } else if (name.equals("extensionStrings")) { + Map extEnc = readMap(reader); + for (Entry entry : extEnc.entrySet()) { + extensions.put(entry.getKey(), entry.getValue()); + } + } else { + reader.skipValue(); } - } else { + break; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); return new OAuth2Request(requestParameters, clientId, authorities, approved, scope, resourceIds, redirectUri, responseTypes, extensions); } - + /** * @param reader * @return - * @throws IOException + * @throws IOException */ private SavedUserAuthentication readSavedUserAuthentication(JsonReader reader) throws IOException { SavedUserAuthentication savedUserAuth = new SavedUserAuthentication(); reader.beginObject(); - + while (reader.hasNext()) { switch(reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("name")) { - savedUserAuth.setName(reader.nextString()); - } else if (name.equals("sourceClass")) { - savedUserAuth.setSourceClass(reader.nextString()); - } else if (name.equals("authenticated")) { - savedUserAuth.setAuthenticated(reader.nextBoolean()); - } else if (name.equals("authorities")) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("name")) { + savedUserAuth.setName(reader.nextString()); + } else if (name.equals("sourceClass")) { + savedUserAuth.setSourceClass(reader.nextString()); + } else if (name.equals("authenticated")) { + savedUserAuth.setAuthenticated(reader.nextBoolean()); + } else if (name.equals("authorities")) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + savedUserAuth.setAuthorities(authorities); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - savedUserAuth.setAuthorities(authorities); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } - + reader.endObject(); return savedUserAuth; } - Map grantOldToNewIdMap = new HashMap<>(); - Map> grantToAccessTokensRefs = new HashMap<>(); - /** * @param reader * @throws IOException @@ -519,61 +529,59 @@ private void readGrants(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("accessDate")) { - Date date = utcToDate(reader.nextString()); - site.setAccessDate(date); - } else if (name.equals("clientId")) { - site.setClientId(reader.nextString()); - } else if (name.equals("creationDate")) { - Date date = utcToDate(reader.nextString()); - site.setCreationDate(date); - } else if (name.equals("timeoutDate")) { - Date date = utcToDate(reader.nextString()); - site.setTimeoutDate(date); - } else if (name.equals("userId")) { - site.setUserId(reader.nextString()); - } else if (name.equals("allowedScopes")) { - Set allowedScopes = readSet(reader); - site.setAllowedScopes(allowedScopes); - } else if (name.equals("whitelistedSiteId")) { - whitelistedSiteId = reader.nextLong(); - } else if (name.equals("approvedAccessTokens")) { - tokenIds = readSet(reader); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("accessDate")) { + Date date = utcToDate(reader.nextString()); + site.setAccessDate(date); + } else if (name.equals("clientId")) { + site.setClientId(reader.nextString()); + } else if (name.equals("creationDate")) { + Date date = utcToDate(reader.nextString()); + site.setCreationDate(date); + } else if (name.equals("timeoutDate")) { + Date date = utcToDate(reader.nextString()); + site.setTimeoutDate(date); + } else if (name.equals("userId")) { + site.setUserId(reader.nextString()); + } else if (name.equals("allowedScopes")) { + Set allowedScopes = readSet(reader); + site.setAllowedScopes(allowedScopes); + } else if (name.equals("whitelistedSiteId")) { + whitelistedSiteId = reader.nextLong(); + } else if (name.equals("approvedAccessTokens")) { + tokenIds = readSet(reader); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = approvedSiteRepository.save(site).getId(); - grantOldToNewIdMap.put(currentId, newId); + maps.getGrantOldToNewIdMap().put(currentId, newId); if (whitelistedSiteId != null) { logger.debug("Ignoring whitelisted site marker on approved site."); } if (tokenIds != null) { - grantToAccessTokensRefs.put(currentId, tokenIds); + maps.getGrantToAccessTokensRefs().put(currentId, tokenIds); } logger.debug("Read grant {}", currentId); } reader.endArray(); logger.info("Done reading grants"); } - Map whitelistedSiteOldToNewIdMap = new HashMap<>(); - /** * @param reader * @throws IOException @@ -586,33 +594,33 @@ private void readWhitelistedSites(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals("id")) { - currentId = reader.nextLong(); - } else if (name.equals("clientId")) { - wlSite.setClientId(reader.nextString()); - } else if (name.equals("creatorUserId")) { - wlSite.setCreatorUserId(reader.nextString()); - } else if (name.equals("allowedScopes")) { - Set allowedScopes = readSet(reader); - wlSite.setAllowedScopes(allowedScopes); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals("id")) { + currentId = reader.nextLong(); + } else if (name.equals("clientId")) { + wlSite.setClientId(reader.nextString()); + } else if (name.equals("creatorUserId")) { + wlSite.setCreatorUserId(reader.nextString()); + } else if (name.equals("allowedScopes")) { + Set allowedScopes = readSet(reader); + wlSite.setAllowedScopes(allowedScopes); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = wlSiteRepository.save(wlSite).getId(); - whitelistedSiteOldToNewIdMap.put(currentId, newId); + maps.getWhitelistedSiteOldToNewIdMap().put(currentId, newId); } reader.endArray(); logger.info("Done reading whitelisted sites"); @@ -629,23 +637,23 @@ private void readBlacklistedSites(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals("id")) { - reader.skipValue(); - } else if (name.equals("uri")) { - blSite.setUri(reader.nextString()); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals("id")) { + reader.skipValue(); + } else if (name.equals("uri")) { + blSite.setUri(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -666,125 +674,125 @@ private void readClients(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("clientId")) { - client.setClientId(reader.nextString()); - } else if (name.equals("resourceIds")) { - Set resourceIds = readSet(reader); - client.setResourceIds(resourceIds); - } else if (name.equals("secret")) { - client.setClientSecret(reader.nextString()); - } else if (name.equals("scope")) { - Set scope = readSet(reader); - client.setScope(scope); - } else if (name.equals("authorities")) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet<>(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("clientId")) { + client.setClientId(reader.nextString()); + } else if (name.equals("resourceIds")) { + Set resourceIds = readSet(reader); + client.setResourceIds(resourceIds); + } else if (name.equals("secret")) { + client.setClientSecret(reader.nextString()); + } else if (name.equals("scope")) { + Set scope = readSet(reader); + client.setScope(scope); + } else if (name.equals("authorities")) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet<>(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + client.setAuthorities(authorities); + } else if (name.equals("accessTokenValiditySeconds")) { + client.setAccessTokenValiditySeconds(reader.nextInt()); + } else if (name.equals("refreshTokenValiditySeconds")) { + client.setRefreshTokenValiditySeconds(reader.nextInt()); + } else if (name.equals("redirectUris")) { + Set redirectUris = readSet(reader); + client.setRedirectUris(redirectUris); + } else if (name.equals("name")) { + client.setClientName(reader.nextString()); + } else if (name.equals("uri")) { + client.setClientUri(reader.nextString()); + } else if (name.equals("logoUri")) { + client.setLogoUri(reader.nextString()); + } else if (name.equals("contacts")) { + Set contacts = readSet(reader); + client.setContacts(contacts); + } else if (name.equals("tosUri")) { + client.setTosUri(reader.nextString()); + } else if (name.equals("tokenEndpointAuthMethod")) { + AuthMethod am = AuthMethod.getByValue(reader.nextString()); + client.setTokenEndpointAuthMethod(am); + } else if (name.equals("grantTypes")) { + Set grantTypes = readSet(reader); + client.setGrantTypes(grantTypes); + } else if (name.equals("responseTypes")) { + Set responseTypes = readSet(reader); + client.setResponseTypes(responseTypes); + } else if (name.equals("policyUri")) { + client.setPolicyUri(reader.nextString()); + } else if (name.equals("applicationType")) { + AppType appType = AppType.getByValue(reader.nextString()); + client.setApplicationType(appType); + } else if (name.equals("sectorIdentifierUri")) { + client.setSectorIdentifierUri(reader.nextString()); + } else if (name.equals("subjectType")) { + SubjectType st = SubjectType.getByValue(reader.nextString()); + client.setSubjectType(st); + } else if (name.equals("jwks_uri")) { + client.setJwksUri(reader.nextString()); + } else if (name.equals("requestObjectSigningAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setRequestObjectSigningAlg(alg); + } else if (name.equals("userInfoEncryptedResponseAlg")) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setUserInfoEncryptedResponseAlg(alg); + } else if (name.equals("userInfoEncryptedResponseEnc")) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setUserInfoEncryptedResponseEnc(alg); + } else if (name.equals("userInfoSignedResponseAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setUserInfoSignedResponseAlg(alg); + } else if (name.equals("idTokenSignedResonseAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setIdTokenSignedResponseAlg(alg); + } else if (name.equals("idTokenEncryptedResponseAlg")) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setIdTokenEncryptedResponseAlg(alg); + } else if (name.equals("idTokenEncryptedResponseEnc")) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setIdTokenEncryptedResponseEnc(alg); + } else if (name.equals("tokenEndpointAuthSigningAlg")) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setTokenEndpointAuthSigningAlg(alg); + } else if (name.equals("defaultMaxAge")) { + client.setDefaultMaxAge(reader.nextInt()); + } else if (name.equals("requireAuthTime")) { + client.setRequireAuthTime(reader.nextBoolean()); + } else if (name.equals("defaultACRValues")) { + Set defaultACRvalues = readSet(reader); + client.setDefaultACRvalues(defaultACRvalues); + } else if (name.equals("initiateLoginUri")) { + client.setInitiateLoginUri(reader.nextString()); + } else if (name.equals("postLogoutRedirectUri")) { + HashSet postLogoutUris = Sets.newHashSet(reader.nextString()); + client.setPostLogoutRedirectUris(postLogoutUris); + } else if (name.equals("requestUris")) { + Set requestUris = readSet(reader); + client.setRequestUris(requestUris); + } else if (name.equals("description")) { + client.setClientDescription(reader.nextString()); + } else if (name.equals("allowIntrospection")) { + client.setAllowIntrospection(reader.nextBoolean()); + } else if (name.equals("reuseRefreshToken")) { + client.setReuseRefreshToken(reader.nextBoolean()); + } else if (name.equals("dynamicallyRegistered")) { + client.setDynamicallyRegistered(reader.nextBoolean()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - client.setAuthorities(authorities); - } else if (name.equals("accessTokenValiditySeconds")) { - client.setAccessTokenValiditySeconds(reader.nextInt()); - } else if (name.equals("refreshTokenValiditySeconds")) { - client.setRefreshTokenValiditySeconds(reader.nextInt()); - } else if (name.equals("redirectUris")) { - Set redirectUris = readSet(reader); - client.setRedirectUris(redirectUris); - } else if (name.equals("name")) { - client.setClientName(reader.nextString()); - } else if (name.equals("uri")) { - client.setClientUri(reader.nextString()); - } else if (name.equals("logoUri")) { - client.setLogoUri(reader.nextString()); - } else if (name.equals("contacts")) { - Set contacts = readSet(reader); - client.setContacts(contacts); - } else if (name.equals("tosUri")) { - client.setTosUri(reader.nextString()); - } else if (name.equals("tokenEndpointAuthMethod")) { - AuthMethod am = AuthMethod.getByValue(reader.nextString()); - client.setTokenEndpointAuthMethod(am); - } else if (name.equals("grantTypes")) { - Set grantTypes = readSet(reader); - client.setGrantTypes(grantTypes); - } else if (name.equals("responseTypes")) { - Set responseTypes = readSet(reader); - client.setResponseTypes(responseTypes); - } else if (name.equals("policyUri")) { - client.setPolicyUri(reader.nextString()); - } else if (name.equals("applicationType")) { - AppType appType = AppType.getByValue(reader.nextString()); - client.setApplicationType(appType); - } else if (name.equals("sectorIdentifierUri")) { - client.setSectorIdentifierUri(reader.nextString()); - } else if (name.equals("subjectType")) { - SubjectType st = SubjectType.getByValue(reader.nextString()); - client.setSubjectType(st); - } else if (name.equals("jwks_uri")) { - client.setJwksUri(reader.nextString()); - } else if (name.equals("requestObjectSigningAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setRequestObjectSigningAlg(alg); - } else if (name.equals("userInfoEncryptedResponseAlg")) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setUserInfoEncryptedResponseAlg(alg); - } else if (name.equals("userInfoEncryptedResponseEnc")) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setUserInfoEncryptedResponseEnc(alg); - } else if (name.equals("userInfoSignedResponseAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setUserInfoSignedResponseAlg(alg); - } else if (name.equals("idTokenSignedResonseAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setIdTokenSignedResponseAlg(alg); - } else if (name.equals("idTokenEncryptedResponseAlg")) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setIdTokenEncryptedResponseAlg(alg); - } else if (name.equals("idTokenEncryptedResponseEnc")) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setIdTokenEncryptedResponseEnc(alg); - } else if (name.equals("tokenEndpointAuthSigningAlg")) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setTokenEndpointAuthSigningAlg(alg); - } else if (name.equals("defaultMaxAge")) { - client.setDefaultMaxAge(reader.nextInt()); - } else if (name.equals("requireAuthTime")) { - client.setRequireAuthTime(reader.nextBoolean()); - } else if (name.equals("defaultACRValues")) { - Set defaultACRvalues = readSet(reader); - client.setDefaultACRvalues(defaultACRvalues); - } else if (name.equals("initiateLoginUri")) { - client.setInitiateLoginUri(reader.nextString()); - } else if (name.equals("postLogoutRedirectUri")) { - HashSet postLogoutUris = Sets.newHashSet(reader.nextString()); - client.setPostLogoutRedirectUris(postLogoutUris); - } else if (name.equals("requestUris")) { - Set requestUris = readSet(reader); - client.setRequestUris(requestUris); - } else if (name.equals("description")) { - client.setClientDescription(reader.nextString()); - } else if (name.equals("allowIntrospection")) { - client.setAllowIntrospection(reader.nextBoolean()); - } else if (name.equals("reuseRefreshToken")) { - client.setReuseRefreshToken(reader.nextBoolean()); - } else if (name.equals("dynamicallyRegistered")) { - client.setDynamicallyRegistered(reader.nextBoolean()); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -808,36 +816,36 @@ private void readSystemScopes(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals("value")) { - scope.setValue(reader.nextString()); - } else if (name.equals("description")) { - scope.setDescription(reader.nextString()); - } else if (name.equals("allowDynReg")) { - // previously "allowDynReg" scopes are now tagged as "not restricted" and vice versa - scope.setRestricted(!reader.nextBoolean()); - } else if (name.equals("defaultScope")) { - scope.setDefaultScope(reader.nextBoolean()); - } else if (name.equals("structured")) { - scope.setStructured(reader.nextBoolean()); - } else if (name.equals("structuredParameter")) { - scope.setStructuredParamDescription(reader.nextString()); - } else if (name.equals("icon")) { - scope.setIcon(reader.nextString()); - } else { - logger.debug("found unexpected entry"); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals("value")) { + scope.setValue(reader.nextString()); + } else if (name.equals("description")) { + scope.setDescription(reader.nextString()); + } else if (name.equals("allowDynReg")) { + // previously "allowDynReg" scopes are now tagged as "not restricted" and vice versa + scope.setRestricted(!reader.nextBoolean()); + } else if (name.equals("defaultScope")) { + scope.setDefaultScope(reader.nextBoolean()); + } else if (name.equals("structured")) { + logger.warn("Found a structured scope, ignoring structure"); + } else if (name.equals("structuredParameter")) { + logger.warn("Found a structured scope, ignoring structure"); + } else if (name.equals("icon")) { + scope.setIcon(reader.nextString()); + } else { + logger.debug("found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -848,79 +856,65 @@ private void readSystemScopes(JsonReader reader) throws IOException { } private void fixObjectReferences() { - for (Long oldRefreshTokenId : refreshTokenToClientRefs.keySet()) { - String clientRef = refreshTokenToClientRefs.get(oldRefreshTokenId); + for (Long oldRefreshTokenId : maps.getRefreshTokenToClientRefs().keySet()) { + String clientRef = maps.getRefreshTokenToClientRefs().get(oldRefreshTokenId); ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); refreshToken.setClient(client); tokenRepository.saveRefreshToken(refreshToken); } - refreshTokenToClientRefs.clear(); - for (Long oldRefreshTokenId : refreshTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = refreshTokenToAuthHolderRefs.get(oldRefreshTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); + for (Long oldRefreshTokenId : maps.getRefreshTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getRefreshTokenToAuthHolderRefs().get(oldRefreshTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); refreshToken.setAuthenticationHolder(authHolder); tokenRepository.saveRefreshToken(refreshToken); } - refreshTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToClientRefs.keySet()) { - String clientRef = accessTokenToClientRefs.get(oldAccessTokenId); + for (Long oldAccessTokenId : maps.getAccessTokenToClientRefs().keySet()) { + String clientRef = maps.getAccessTokenToClientRefs().get(oldAccessTokenId); ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setClient(client); tokenRepository.saveAccessToken(accessToken); } - accessTokenToClientRefs.clear(); - for (Long oldAccessTokenId : accessTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = accessTokenToAuthHolderRefs.get(oldAccessTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); + maps.getAccessTokenToClientRefs().clear(); + for (Long oldAccessTokenId : maps.getAccessTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getAccessTokenToAuthHolderRefs().get(oldAccessTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setAuthenticationHolder(authHolder); tokenRepository.saveAccessToken(accessToken); } - accessTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToRefreshTokenRefs.keySet()) { - Long oldRefreshTokenId = accessTokenToRefreshTokenRefs.get(oldAccessTokenId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + for (Long oldAccessTokenId : maps.getAccessTokenToRefreshTokenRefs().keySet()) { + Long oldRefreshTokenId = maps.getAccessTokenToRefreshTokenRefs().get(oldAccessTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setRefreshToken(refreshToken); tokenRepository.saveAccessToken(accessToken); } - accessTokenToRefreshTokenRefs.clear(); - refreshTokenOldToNewIdMap.clear(); - for (Long oldAccessTokenId : accessTokenToIdTokenRefs.keySet()) { - Long oldIdTokenId = accessTokenToIdTokenRefs.get(oldAccessTokenId); - Long newIdTokenId = accessTokenOldToNewIdMap.get(oldIdTokenId); - OAuth2AccessTokenEntity idToken = tokenRepository.getAccessTokenById(newIdTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setIdToken(idToken); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToIdTokenRefs.clear(); - for (Long oldGrantId : grantToAccessTokensRefs.keySet()) { - Set oldAccessTokenIds = grantToAccessTokensRefs.get(oldGrantId); - Set tokens = new HashSet<>(); + for (Long oldGrantId : maps.getGrantToAccessTokensRefs().keySet()) { + Set oldAccessTokenIds = maps.getGrantToAccessTokensRefs().get(oldGrantId); + + Long newGrantId = maps.getGrantOldToNewIdMap().get(oldGrantId); + ApprovedSite site = approvedSiteRepository.getById(newGrantId); + for(Long oldTokenId : oldAccessTokenIds) { - Long newTokenId = accessTokenOldToNewIdMap.get(oldTokenId); - tokens.add(tokenRepository.getAccessTokenById(newTokenId)); + Long newTokenId = maps.getAccessTokenOldToNewIdMap().get(oldTokenId); + OAuth2AccessTokenEntity token = tokenRepository.getAccessTokenById(newTokenId); + token.setApprovedSite(site); + tokenRepository.saveAccessToken(token); } - Long newGrantId = grantOldToNewIdMap.get(oldGrantId); - ApprovedSite site = approvedSiteRepository.getById(newGrantId); - site.setApprovedAccessTokens(tokens); + approvedSiteRepository.save(site); } - accessTokenOldToNewIdMap.clear(); - grantOldToNewIdMap.clear(); } - + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java index 26c2009809..8cbe31232d 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,14 +15,15 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mitre.util.JsonUtils.readMap; +import static org.mitre.util.JsonUtils.readSet; + import java.io.IOException; -import java.io.Serializable; import java.text.ParseException; +import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; +import java.util.List; import java.util.Set; import org.mitre.oauth2.model.AuthenticationHolderEntity; @@ -46,6 +46,8 @@ import org.mitre.openid.connect.repository.BlacklistedSiteRepository; import org.mitre.openid.connect.repository.WhitelistedSiteRepository; import org.mitre.openid.connect.service.MITREidDataService; +import org.mitre.openid.connect.service.MITREidDataServiceExtension; +import org.mitre.openid.connect.service.MITREidDataServiceMaps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -62,10 +64,6 @@ import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jwt.JWTParser; -import static org.mitre.util.JsonUtils.readMap; -import static org.mitre.util.JsonUtils.readSet; -import static org.mitre.util.JsonUtils.writeNullSafeArray; - /** * * Data service to import and export MITREid 1.2 configuration. @@ -137,12 +135,12 @@ public class MITREidDataService_1_2 extends MITREidDataServiceSupport implements private static final String REQUEST_PARAMETERS = "requestParameters"; private static final String TYPE = "type"; private static final String SCOPE = "scope"; - private static final String ID_TOKEN_ID = "idTokenId"; private static final String REFRESH_TOKEN_ID = "refreshTokenId"; private static final String VALUE = "value"; private static final String AUTHENTICATION_HOLDER_ID = "authenticationHolderId"; private static final String CLIENT_ID = "clientId"; private static final String EXPIRATION = "expiration"; + private static final String CLAIMS_REDIRECT_URIS = "claimsRedirectUris"; private static final String ID = "id"; /** * Logger for this class @@ -162,372 +160,25 @@ public class MITREidDataService_1_2 extends MITREidDataServiceSupport implements private OAuth2TokenRepository tokenRepository; @Autowired private SystemScopeRepository sysScopeRepository; - - /* (non-Javadoc) - * @see org.mitre.openid.connect.service.MITREidDataService#export(com.google.gson.stream.JsonWriter) - */ - @Override - public void exportData(JsonWriter writer) throws IOException { - - // version tag at the root - writer.name(MITREID_CONNECT_1_2); - - writer.beginObject(); - - // clients list - writer.name(CLIENTS); - writer.beginArray(); - writeClients(writer); - writer.endArray(); - - writer.name(GRANTS); - writer.beginArray(); - writeGrants(writer); - writer.endArray(); - - writer.name(WHITELISTEDSITES); - writer.beginArray(); - writeWhitelistedSites(writer); - writer.endArray(); - - writer.name(BLACKLISTEDSITES); - writer.beginArray(); - writeBlacklistedSites(writer); - writer.endArray(); - - writer.name(AUTHENTICATIONHOLDERS); - writer.beginArray(); - writeAuthenticationHolders(writer); - writer.endArray(); - - writer.name(ACCESSTOKENS); - writer.beginArray(); - writeAccessTokens(writer); - writer.endArray(); - - writer.name(REFRESHTOKENS); - writer.beginArray(); - writeRefreshTokens(writer); - writer.endArray(); - - writer.name(SYSTEMSCOPES); - writer.beginArray(); - writeSystemScopes(writer); - writer.endArray(); - - writer.endObject(); // end mitreid-connect-1.2 - } - - /** - * @param writer - */ - private void writeRefreshTokens(JsonWriter writer) throws IOException { - for (OAuth2RefreshTokenEntity token : tokenRepository.getAllRefreshTokens()) { - writer.beginObject(); - writer.name(ID).value(token.getId()); - writer.name(EXPIRATION).value(toUTCString(token.getExpiration())); - writer.name(CLIENT_ID) - .value((token.getClient() != null) ? token.getClient().getClientId() : null); - writer.name(AUTHENTICATION_HOLDER_ID) - .value((token.getAuthenticationHolder() != null) ? token.getAuthenticationHolder().getId() : null); - writer.name(VALUE).value(token.getValue()); - writer.endObject(); - logger.debug("Wrote refresh token {}", token.getId()); - } - logger.info("Done writing refresh tokens"); - } - - /** - * @param writer - */ - private void writeAccessTokens(JsonWriter writer) throws IOException { - for (OAuth2AccessTokenEntity token : tokenRepository.getAllAccessTokens()) { - writer.beginObject(); - writer.name(ID).value(token.getId()); - writer.name(EXPIRATION).value(toUTCString(token.getExpiration())); - writer.name(CLIENT_ID) - .value((token.getClient() != null) ? token.getClient().getClientId() : null); - writer.name(AUTHENTICATION_HOLDER_ID) - .value((token.getAuthenticationHolder() != null) ? token.getAuthenticationHolder().getId() : null); - writer.name(REFRESH_TOKEN_ID) - .value((token.getRefreshToken() != null) ? token.getRefreshToken().getId() : null); - writer.name(ID_TOKEN_ID) - .value((token.getIdToken() != null) ? token.getIdToken().getId() : null); - writer.name(SCOPE); - writer.beginArray(); - for (String s : token.getScope()) { - writer.value(s); - } - writer.endArray(); - writer.name(TYPE).value(token.getTokenType()); - writer.name(VALUE).value(token.getValue()); - writer.endObject(); - logger.debug("Wrote access token {}", token.getId()); - } - logger.info("Done writing access tokens"); - } + @Autowired(required = false) + private List extensions = Collections.emptyList(); - /** - * @param writer - */ - private void writeAuthenticationHolders(JsonWriter writer) throws IOException { - for (AuthenticationHolderEntity holder : authHolderRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(holder.getId()); - - writer.name(REQUEST_PARAMETERS); - writer.beginObject(); - for (Entry entry : holder.getRequestParameters().entrySet()) { - writer.name(entry.getKey()).value(entry.getValue()); - } - writer.endObject(); - writer.name(CLIENT_ID).value(holder.getClientId()); - Set scope = holder.getScope(); - writer.name(SCOPE); - writer.beginArray(); - for (String s : scope) { - writer.value(s); - } - writer.endArray(); - writer.name(RESOURCE_IDS); - writer.beginArray(); - if (holder.getResourceIds() != null) { - for (String s : holder.getResourceIds()) { - writer.value(s); - } - } - writer.endArray(); - writer.name(AUTHORITIES); - writer.beginArray(); - for (GrantedAuthority authority : holder.getAuthorities()) { - writer.value(authority.getAuthority()); - } - writer.endArray(); - writer.name(APPROVED).value(holder.isApproved()); - writer.name(REDIRECT_URI).value(holder.getRedirectUri()); - writer.name(RESPONSE_TYPES); - writer.beginArray(); - for (String s : holder.getResponseTypes()) { - writer.value(s); - } - writer.endArray(); - writer.name(EXTENSIONS); - writer.beginObject(); - for (Entry entry : holder.getExtensions().entrySet()) { - // while the extension map itself is Serializable, we enforce storage of Strings - if (entry.getValue() instanceof String) { - writer.name(entry.getKey()).value((String) entry.getValue()); - } else { - logger.warn("Skipping non-string extension: " + entry); - } - } - writer.endObject(); + private MITREidDataServiceMaps maps = new MITREidDataServiceMaps(); - writer.name(SAVED_USER_AUTHENTICATION); - if (holder.getUserAuth() != null) { - writer.beginObject(); - writer.name(NAME).value(holder.getUserAuth().getName()); - writer.name(SOURCE_CLASS).value(holder.getUserAuth().getSourceClass()); - writer.name(AUTHENTICATED).value(holder.getUserAuth().isAuthenticated()); - writer.name(AUTHORITIES); - writer.beginArray(); - for (GrantedAuthority authority : holder.getUserAuth().getAuthorities()) { - writer.value(authority.getAuthority()); - } - writer.endArray(); - - writer.endObject(); - } else { - writer.nullValue(); - } - - - writer.endObject(); - logger.debug("Wrote authentication holder {}", holder.getId()); - } - logger.info("Done writing authentication holders"); - } + private static final String THIS_VERSION = MITREID_CONNECT_1_2; - /** - * @param writer - */ - private void writeGrants(JsonWriter writer) throws IOException { - for (ApprovedSite site : approvedSiteRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(site.getId()); - writer.name(ACCESS_DATE).value(toUTCString(site.getAccessDate())); - writer.name(CLIENT_ID).value(site.getClientId()); - writer.name(CREATION_DATE).value(toUTCString(site.getCreationDate())); - writer.name(TIMEOUT_DATE).value(toUTCString(site.getTimeoutDate())); - writer.name(USER_ID).value(site.getUserId()); - writer.name(ALLOWED_SCOPES); - writeNullSafeArray(writer, site.getAllowedScopes()); - Set tokens = site.getApprovedAccessTokens(); - writer.name(APPROVED_ACCESS_TOKENS); - writer.beginArray(); - for (OAuth2AccessTokenEntity token : tokens) { - writer.value(token.getId()); - } - writer.endArray(); - writer.endObject(); - logger.debug("Wrote grant {}", site.getId()); - } - logger.info("Done writing grants"); - } - - /** - * @param writer - */ - private void writeWhitelistedSites(JsonWriter writer) throws IOException { - for (WhitelistedSite wlSite : wlSiteRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(wlSite.getId()); - writer.name(CLIENT_ID).value(wlSite.getClientId()); - writer.name(CREATOR_USER_ID).value(wlSite.getCreatorUserId()); - writer.name(ALLOWED_SCOPES); - writeNullSafeArray(writer, wlSite.getAllowedScopes()); - writer.endObject(); - logger.debug("Wrote whitelisted site {}", wlSite.getId()); - } - logger.info("Done writing whitelisted sites"); - } - - /** - * @param writer - */ - private void writeBlacklistedSites(JsonWriter writer) throws IOException { - for (BlacklistedSite blSite : blSiteRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(blSite.getId()); - writer.name(URI).value(blSite.getUri()); - writer.endObject(); - logger.debug("Wrote blacklisted site {}", blSite.getId()); - } - logger.info("Done writing blacklisted sites"); + @Override + public boolean supportsVersion(String version) { + return THIS_VERSION.equals(version); } - /** - * @param writer + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataService#export(com.google.gson.stream.JsonWriter) */ - private void writeClients(JsonWriter writer) { - for (ClientDetailsEntity client : clientRepository.getAllClients()) { - try { - writer.beginObject(); - writer.name(CLIENT_ID).value(client.getClientId()); - writer.name(RESOURCE_IDS); - writeNullSafeArray(writer, client.getResourceIds()); - - writer.name(SECRET).value(client.getClientSecret()); - - writer.name(SCOPE); - writeNullSafeArray(writer, client.getScope()); - - writer.name(AUTHORITIES); - writer.beginArray(); - for (GrantedAuthority authority : client.getAuthorities()) { - writer.value(authority.getAuthority()); - } - writer.endArray(); - writer.name(ACCESS_TOKEN_VALIDITY_SECONDS).value(client.getAccessTokenValiditySeconds()); - writer.name(REFRESH_TOKEN_VALIDITY_SECONDS).value(client.getRefreshTokenValiditySeconds()); - writer.name(REDIRECT_URIS); - writeNullSafeArray(writer, client.getRedirectUris()); - writer.name(NAME).value(client.getClientName()); - writer.name(URI).value(client.getClientUri()); - writer.name(LOGO_URI).value(client.getLogoUri()); - writer.name(CONTACTS); - writeNullSafeArray(writer, client.getContacts()); - writer.name(TOS_URI).value(client.getTosUri()); - writer.name(TOKEN_ENDPOINT_AUTH_METHOD) - .value((client.getTokenEndpointAuthMethod() != null) ? client.getTokenEndpointAuthMethod().getValue() : null); - writer.name(GRANT_TYPES); - writer.beginArray(); - for (String s : client.getGrantTypes()) { - writer.value(s); - } - writer.endArray(); - writer.name(RESPONSE_TYPES); - writer.beginArray(); - for (String s : client.getResponseTypes()) { - writer.value(s); - } - writer.endArray(); - writer.name(POLICY_URI).value(client.getPolicyUri()); - writer.name(JWKS_URI).value(client.getJwksUri()); - writer.name(JWKS).value((client.getJwks() != null) ? client.getJwks().toString() : null); - writer.name(APPLICATION_TYPE) - .value((client.getApplicationType() != null) ? client.getApplicationType().getValue() : null); - writer.name(SECTOR_IDENTIFIER_URI).value(client.getSectorIdentifierUri()); - writer.name(SUBJECT_TYPE) - .value((client.getSubjectType() != null) ? client.getSubjectType().getValue() : null); - writer.name(REQUEST_OBJECT_SIGNING_ALG) - .value((client.getRequestObjectSigningAlg() != null) ? client.getRequestObjectSigningAlg().getName() : null); - writer.name(ID_TOKEN_SIGNED_RESPONSE_ALG) - .value((client.getIdTokenSignedResponseAlg() != null) ? client.getIdTokenSignedResponseAlg().getName() : null); - writer.name(ID_TOKEN_ENCRYPTED_RESPONSE_ALG) - .value((client.getIdTokenEncryptedResponseAlg() != null) ? client.getIdTokenEncryptedResponseAlg().getName() : null); - writer.name(ID_TOKEN_ENCRYPTED_RESPONSE_ENC) - .value((client.getIdTokenEncryptedResponseEnc() != null) ? client.getIdTokenEncryptedResponseEnc().getName() : null); - writer.name(USER_INFO_SIGNED_RESPONSE_ALG) - .value((client.getUserInfoSignedResponseAlg() != null) ? client.getUserInfoSignedResponseAlg().getName() : null); - writer.name(USER_INFO_ENCRYPTED_RESPONSE_ALG) - .value((client.getUserInfoEncryptedResponseAlg() != null) ? client.getUserInfoEncryptedResponseAlg().getName() : null); - writer.name(USER_INFO_ENCRYPTED_RESPONSE_ENC) - .value((client.getUserInfoEncryptedResponseEnc() != null) ? client.getUserInfoEncryptedResponseEnc().getName() : null); - writer.name(TOKEN_ENDPOINT_AUTH_SIGNING_ALG) - .value((client.getTokenEndpointAuthSigningAlg() != null) ? client.getTokenEndpointAuthSigningAlg().getName() : null); - writer.name(DEFAULT_MAX_AGE).value(client.getDefaultMaxAge()); - Boolean requireAuthTime = null; - try { - requireAuthTime = client.getRequireAuthTime(); - } catch (NullPointerException e) { - } - if (requireAuthTime != null) { - writer.name(REQUIRE_AUTH_TIME).value(requireAuthTime); - } - writer.name(DEFAULT_ACR_VALUES); - writeNullSafeArray(writer, client.getDefaultACRvalues()); - writer.name(INTITATE_LOGIN_URI).value(client.getInitiateLoginUri()); - writer.name(POST_LOGOUT_REDIRECT_URI); - writeNullSafeArray(writer, client.getPostLogoutRedirectUris()); - writer.name(REQUEST_URIS); - writeNullSafeArray(writer, client.getRequestUris()); - writer.name(DESCRIPTION).value(client.getClientDescription()); - writer.name(ALLOW_INTROSPECTION).value(client.isAllowIntrospection()); - writer.name(REUSE_REFRESH_TOKEN).value(client.isReuseRefreshToken()); - writer.name(CLEAR_ACCESS_TOKENS_ON_REFRESH).value(client.isClearAccessTokensOnRefresh()); - writer.name(DYNAMICALLY_REGISTERED).value(client.isDynamicallyRegistered()); - writer.endObject(); - logger.debug("Wrote client {}", client.getId()); - } catch (IOException ex) { - logger.error("Unable to write client {}", client.getId(), ex); - } - } - logger.info("Done writing clients"); - } + @Override + public void exportData(JsonWriter writer) throws IOException { - /** - * @param writer - */ - private void writeSystemScopes(JsonWriter writer) { - for (SystemScope sysScope : sysScopeRepository.getAll()) { - try { - writer.beginObject(); - writer.name(ID).value(sysScope.getId()); - writer.name(DESCRIPTION).value(sysScope.getDescription()); - writer.name(ICON).value(sysScope.getIcon()); - writer.name(VALUE).value(sysScope.getValue()); - writer.name(RESTRICTED).value(sysScope.isRestricted()); - writer.name(STRUCTURED).value(sysScope.isStructured()); - writer.name(STRUCTURED_PARAMETER).value(sysScope.getStructuredParamDescription()); - writer.name(DEFAULT_SCOPE).value(sysScope.isDefaultScope()); - writer.endObject(); - logger.debug("Wrote system scope {}", sysScope.getId()); - } catch (IOException ex) { - logger.error("Unable to write system scope {}", sysScope.getId(), ex); - } - } - logger.info("Done writing system scopes"); + throw new UnsupportedOperationException("Can not export 1.2 format from this version."); } /* (non-Javadoc) @@ -544,46 +195,55 @@ public void importData(JsonReader reader) throws IOException { while (reader.hasNext()) { JsonToken tok = reader.peek(); switch (tok) { - case NAME: - String name = reader.nextName(); - // find out which member it is - if (name.equals(CLIENTS)) { - readClients(reader); - } else if (name.equals(GRANTS)) { - readGrants(reader); - } else if (name.equals(WHITELISTEDSITES)) { - readWhitelistedSites(reader); - } else if (name.equals(BLACKLISTEDSITES)) { - readBlacklistedSites(reader); - } else if (name.equals(AUTHENTICATIONHOLDERS)) { - readAuthenticationHolders(reader); - } else if (name.equals(ACCESSTOKENS)) { - readAccessTokens(reader); - } else if (name.equals(REFRESHTOKENS)) { - readRefreshTokens(reader); - } else if (name.equals(SYSTEMSCOPES)) { - readSystemScopes(reader); - } else { - // unknown token, skip it + case NAME: + String name = reader.nextName(); + // find out which member it is + if (name.equals(CLIENTS)) { + readClients(reader); + } else if (name.equals(GRANTS)) { + readGrants(reader); + } else if (name.equals(WHITELISTEDSITES)) { + readWhitelistedSites(reader); + } else if (name.equals(BLACKLISTEDSITES)) { + readBlacklistedSites(reader); + } else if (name.equals(AUTHENTICATIONHOLDERS)) { + readAuthenticationHolders(reader); + } else if (name.equals(ACCESSTOKENS)) { + readAccessTokens(reader); + } else if (name.equals(REFRESHTOKENS)) { + readRefreshTokens(reader); + } else if (name.equals(SYSTEMSCOPES)) { + readSystemScopes(reader); + } else { + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.importExtensionData(name, reader); + break; + } + } + // unknown token, skip it + reader.skipValue(); + } + break; + case END_OBJECT: + // the object ended, we're done here + reader.endObject(); + continue; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - case END_OBJECT: - // the object ended, we're done here - reader.endObject(); - continue; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } fixObjectReferences(); + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.fixExtensionObjectReferences(maps); + break; + } + } + maps.clearAll(); } - private Map refreshTokenToClientRefs = new HashMap(); - private Map refreshTokenToAuthHolderRefs = new HashMap(); - private Map refreshTokenOldToNewIdMap = new HashMap(); - /** * @param reader * @throws IOException @@ -602,55 +262,49 @@ private void readRefreshTokens(JsonReader reader) throws IOException { Long authHolderId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(EXPIRATION)) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals(VALUE)) { - String value = reader.nextString(); - try { - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(EXPIRATION)) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals(VALUE)) { + String value = reader.nextString(); + try { + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals(CLIENT_ID)) { + clientId = reader.nextString(); + } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { + authHolderId = reader.nextLong(); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - } else if (name.equals(CLIENT_ID)) { - clientId = reader.nextString(); - } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { - authHolderId = reader.nextLong(); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = tokenRepository.saveRefreshToken(token).getId(); - refreshTokenToClientRefs.put(currentId, clientId); - refreshTokenToAuthHolderRefs.put(currentId, authHolderId); - refreshTokenOldToNewIdMap.put(currentId, newId); + maps.getRefreshTokenToClientRefs().put(currentId, clientId); + maps.getRefreshTokenToAuthHolderRefs().put(currentId, authHolderId); + maps.getRefreshTokenOldToNewIdMap().put(currentId, newId); logger.debug("Read refresh token {}", currentId); } reader.endArray(); logger.info("Done reading refresh tokens"); } - private Map accessTokenToClientRefs = new HashMap(); - private Map accessTokenToAuthHolderRefs = new HashMap(); - private Map accessTokenToRefreshTokenRefs = new HashMap(); - private Map accessTokenToIdTokenRefs = new HashMap(); - private Map accessTokenOldToNewIdMap = new HashMap(); - /** * @param reader * @throws IOException @@ -668,70 +322,62 @@ private void readAccessTokens(JsonReader reader) throws IOException { String clientId = null; Long authHolderId = null; Long refreshTokenId = null; - Long idTokenId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(EXPIRATION)) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals(VALUE)) { - String value = reader.nextString(); - try { - // all tokens are JWTs - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(EXPIRATION)) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals(VALUE)) { + String value = reader.nextString(); + try { + // all tokens are JWTs + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals(CLIENT_ID)) { + clientId = reader.nextString(); + } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { + authHolderId = reader.nextLong(); + } else if (name.equals(REFRESH_TOKEN_ID)) { + refreshTokenId = reader.nextLong(); + } else if (name.equals(SCOPE)) { + Set scope = readSet(reader); + token.setScope(scope); + } else if (name.equals(TYPE)) { + token.setTokenType(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - } else if (name.equals(CLIENT_ID)) { - clientId = reader.nextString(); - } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { - authHolderId = reader.nextLong(); - } else if (name.equals(REFRESH_TOKEN_ID)) { - refreshTokenId = reader.nextLong(); - } else if (name.equals(ID_TOKEN_ID)) { - idTokenId = reader.nextLong(); - } else if (name.equals(SCOPE)) { - Set scope = readSet(reader); - token.setScope(scope); - } else if (name.equals(TYPE)) { - token.setTokenType(reader.nextString()); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = tokenRepository.saveAccessToken(token).getId(); - accessTokenToClientRefs.put(currentId, clientId); - accessTokenToAuthHolderRefs.put(currentId, authHolderId); + maps.getAccessTokenToClientRefs().put(currentId, clientId); + maps.getAccessTokenToAuthHolderRefs().put(currentId, authHolderId); if (refreshTokenId != null) { - accessTokenToRefreshTokenRefs.put(currentId, refreshTokenId); - } - if (idTokenId != null) { - accessTokenToIdTokenRefs.put(currentId, idTokenId); + maps.getAccessTokenToRefreshTokenRefs().put(currentId, refreshTokenId); } - accessTokenOldToNewIdMap.put(currentId, newId); + maps.getAccessTokenOldToNewIdMap().put(currentId, newId); logger.debug("Read access token {}", currentId); } reader.endArray(); logger.info("Done reading access tokens"); } - private Map authHolderOldToNewIdMap = new HashMap(); - /** * @param reader * @throws IOException @@ -744,54 +390,54 @@ private void readAuthenticationHolders(JsonReader reader) throws IOException { Long currentId = null; while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(REQUEST_PARAMETERS)) { - ahe.setRequestParameters(readMap(reader)); - } else if (name.equals(CLIENT_ID)) { - ahe.setClientId(reader.nextString()); - } else if (name.equals(SCOPE)) { - ahe.setScope(readSet(reader)); - } else if (name.equals(RESOURCE_IDS)) { - ahe.setResourceIds(readSet(reader)); - } else if (name.equals(AUTHORITIES)) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(REQUEST_PARAMETERS)) { + ahe.setRequestParameters(readMap(reader)); + } else if (name.equals(CLIENT_ID)) { + ahe.setClientId(reader.nextString()); + } else if (name.equals(SCOPE)) { + ahe.setScope(readSet(reader)); + } else if (name.equals(RESOURCE_IDS)) { + ahe.setResourceIds(readSet(reader)); + } else if (name.equals(AUTHORITIES)) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + ahe.setAuthorities(authorities); + } else if (name.equals(APPROVED)) { + ahe.setApproved(reader.nextBoolean()); + } else if (name.equals(REDIRECT_URI)) { + ahe.setRedirectUri(reader.nextString()); + } else if (name.equals(RESPONSE_TYPES)) { + ahe.setResponseTypes(readSet(reader)); + } else if (name.equals(EXTENSIONS)) { + ahe.setExtensions(readMap(reader)); + } else if (name.equals(SAVED_USER_AUTHENTICATION)) { + ahe.setUserAuth(readSavedUserAuthentication(reader)); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - ahe.setAuthorities(authorities); - } else if (name.equals(APPROVED)) { - ahe.setApproved(reader.nextBoolean()); - } else if (name.equals(REDIRECT_URI)) { - ahe.setRedirectUri(reader.nextString()); - } else if (name.equals(RESPONSE_TYPES)) { - ahe.setResponseTypes(readSet(reader)); - } else if (name.equals(EXTENSIONS)) { - ahe.setExtensions(readMap(reader)); - } else if (name.equals(SAVED_USER_AUTHENTICATION)) { - ahe.setUserAuth(readSavedUserAuthentication(reader)); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = authHolderRepository.save(ahe).getId(); - authHolderOldToNewIdMap.put(currentId, newId); + maps.getAuthHolderOldToNewIdMap().put(currentId, newId); logger.debug("Read authentication holder {}", currentId); } reader.endArray(); @@ -801,53 +447,50 @@ private void readAuthenticationHolders(JsonReader reader) throws IOException { /** * @param reader * @return - * @throws IOException + * @throws IOException */ private SavedUserAuthentication readSavedUserAuthentication(JsonReader reader) throws IOException { SavedUserAuthentication savedUserAuth = new SavedUserAuthentication(); reader.beginObject(); - + while (reader.hasNext()) { switch(reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(NAME)) { - savedUserAuth.setName(reader.nextString()); - } else if (name.equals(SOURCE_CLASS)) { - savedUserAuth.setSourceClass(reader.nextString()); - } else if (name.equals(AUTHENTICATED)) { - savedUserAuth.setAuthenticated(reader.nextBoolean()); - } else if (name.equals(AUTHORITIES)) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(NAME)) { + savedUserAuth.setName(reader.nextString()); + } else if (name.equals(SOURCE_CLASS)) { + savedUserAuth.setSourceClass(reader.nextString()); + } else if (name.equals(AUTHENTICATED)) { + savedUserAuth.setAuthenticated(reader.nextBoolean()); + } else if (name.equals(AUTHORITIES)) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + savedUserAuth.setAuthorities(authorities); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - savedUserAuth.setAuthorities(authorities); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } - + reader.endObject(); return savedUserAuth; } - Map grantOldToNewIdMap = new HashMap<>(); - Map> grantToAccessTokensRefs = new HashMap<>(); - /** * @param reader * @throws IOException @@ -861,56 +504,54 @@ private void readGrants(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(ACCESS_DATE)) { - Date date = utcToDate(reader.nextString()); - site.setAccessDate(date); - } else if (name.equals(CLIENT_ID)) { - site.setClientId(reader.nextString()); - } else if (name.equals(CREATION_DATE)) { - Date date = utcToDate(reader.nextString()); - site.setCreationDate(date); - } else if (name.equals(TIMEOUT_DATE)) { - Date date = utcToDate(reader.nextString()); - site.setTimeoutDate(date); - } else if (name.equals(USER_ID)) { - site.setUserId(reader.nextString()); - } else if (name.equals(ALLOWED_SCOPES)) { - Set allowedScopes = readSet(reader); - site.setAllowedScopes(allowedScopes); - } else if (name.equals(APPROVED_ACCESS_TOKENS)) { - tokenIds = readSet(reader); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(ACCESS_DATE)) { + Date date = utcToDate(reader.nextString()); + site.setAccessDate(date); + } else if (name.equals(CLIENT_ID)) { + site.setClientId(reader.nextString()); + } else if (name.equals(CREATION_DATE)) { + Date date = utcToDate(reader.nextString()); + site.setCreationDate(date); + } else if (name.equals(TIMEOUT_DATE)) { + Date date = utcToDate(reader.nextString()); + site.setTimeoutDate(date); + } else if (name.equals(USER_ID)) { + site.setUserId(reader.nextString()); + } else if (name.equals(ALLOWED_SCOPES)) { + Set allowedScopes = readSet(reader); + site.setAllowedScopes(allowedScopes); + } else if (name.equals(APPROVED_ACCESS_TOKENS)) { + tokenIds = readSet(reader); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = approvedSiteRepository.save(site).getId(); - grantOldToNewIdMap.put(currentId, newId); + maps.getGrantOldToNewIdMap().put(currentId, newId); if (tokenIds != null) { - grantToAccessTokensRefs.put(currentId, tokenIds); + maps.getGrantToAccessTokensRefs().put(currentId, tokenIds); } logger.debug("Read grant {}", currentId); } reader.endArray(); logger.info("Done reading grants"); } - Map whitelistedSiteOldToNewIdMap = new HashMap(); - /** * @param reader * @throws IOException @@ -923,33 +564,33 @@ private void readWhitelistedSites(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(CLIENT_ID)) { - wlSite.setClientId(reader.nextString()); - } else if (name.equals(CREATOR_USER_ID)) { - wlSite.setCreatorUserId(reader.nextString()); - } else if (name.equals(ALLOWED_SCOPES)) { - Set allowedScopes = readSet(reader); - wlSite.setAllowedScopes(allowedScopes); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(CLIENT_ID)) { + wlSite.setClientId(reader.nextString()); + } else if (name.equals(CREATOR_USER_ID)) { + wlSite.setCreatorUserId(reader.nextString()); + } else if (name.equals(ALLOWED_SCOPES)) { + Set allowedScopes = readSet(reader); + wlSite.setAllowedScopes(allowedScopes); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); Long newId = wlSiteRepository.save(wlSite).getId(); - whitelistedSiteOldToNewIdMap.put(currentId, newId); + maps.getWhitelistedSiteOldToNewIdMap().put(currentId, newId); } reader.endArray(); logger.info("Done reading whitelisted sites"); @@ -966,23 +607,23 @@ private void readBlacklistedSites(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals(ID)) { - reader.skipValue(); - } else if (name.equals(URI)) { - blSite.setUri(reader.nextString()); - } else { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals(ID)) { + reader.skipValue(); + } else if (name.equals(URI)) { + blSite.setUri(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -1003,133 +644,136 @@ private void readClients(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(CLIENT_ID)) { - client.setClientId(reader.nextString()); - } else if (name.equals(RESOURCE_IDS)) { - Set resourceIds = readSet(reader); - client.setResourceIds(resourceIds); - } else if (name.equals(SECRET)) { - client.setClientSecret(reader.nextString()); - } else if (name.equals(SCOPE)) { - Set scope = readSet(reader); - client.setScope(scope); - } else if (name.equals(AUTHORITIES)) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(CLIENT_ID)) { + client.setClientId(reader.nextString()); + } else if (name.equals(RESOURCE_IDS)) { + Set resourceIds = readSet(reader); + client.setResourceIds(resourceIds); + } else if (name.equals(SECRET)) { + client.setClientSecret(reader.nextString()); + } else if (name.equals(SCOPE)) { + Set scope = readSet(reader); + client.setScope(scope); + } else if (name.equals(AUTHORITIES)) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + client.setAuthorities(authorities); + } else if (name.equals(ACCESS_TOKEN_VALIDITY_SECONDS)) { + client.setAccessTokenValiditySeconds(reader.nextInt()); + } else if (name.equals(REFRESH_TOKEN_VALIDITY_SECONDS)) { + client.setRefreshTokenValiditySeconds(reader.nextInt()); + } else if (name.equals(REDIRECT_URIS)) { + Set redirectUris = readSet(reader); + client.setRedirectUris(redirectUris); + } else if (name.equals(CLAIMS_REDIRECT_URIS)) { + Set claimsRedirectUris = readSet(reader); + client.setClaimsRedirectUris(claimsRedirectUris); + } else if (name.equals(NAME)) { + client.setClientName(reader.nextString()); + } else if (name.equals(URI)) { + client.setClientUri(reader.nextString()); + } else if (name.equals(LOGO_URI)) { + client.setLogoUri(reader.nextString()); + } else if (name.equals(CONTACTS)) { + Set contacts = readSet(reader); + client.setContacts(contacts); + } else if (name.equals(TOS_URI)) { + client.setTosUri(reader.nextString()); + } else if (name.equals(TOKEN_ENDPOINT_AUTH_METHOD)) { + AuthMethod am = AuthMethod.getByValue(reader.nextString()); + client.setTokenEndpointAuthMethod(am); + } else if (name.equals(GRANT_TYPES)) { + Set grantTypes = readSet(reader); + client.setGrantTypes(grantTypes); + } else if (name.equals(RESPONSE_TYPES)) { + Set responseTypes = readSet(reader); + client.setResponseTypes(responseTypes); + } else if (name.equals(POLICY_URI)) { + client.setPolicyUri(reader.nextString()); + } else if (name.equals(APPLICATION_TYPE)) { + AppType appType = AppType.getByValue(reader.nextString()); + client.setApplicationType(appType); + } else if (name.equals(SECTOR_IDENTIFIER_URI)) { + client.setSectorIdentifierUri(reader.nextString()); + } else if (name.equals(SUBJECT_TYPE)) { + SubjectType st = SubjectType.getByValue(reader.nextString()); + client.setSubjectType(st); + } else if (name.equals(JWKS_URI)) { + client.setJwksUri(reader.nextString()); + } else if (name.equals(JWKS)) { + try { + client.setJwks(JWKSet.parse(reader.nextString())); + } catch (ParseException e) { + logger.error("Couldn't parse JWK Set", e); + } + } else if (name.equals(REQUEST_OBJECT_SIGNING_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setRequestObjectSigningAlg(alg); + } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ALG)) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setUserInfoEncryptedResponseAlg(alg); + } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ENC)) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setUserInfoEncryptedResponseEnc(alg); + } else if (name.equals(USER_INFO_SIGNED_RESPONSE_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setUserInfoSignedResponseAlg(alg); + } else if (name.equals(ID_TOKEN_SIGNED_RESPONSE_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setIdTokenSignedResponseAlg(alg); + } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ALG)) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setIdTokenEncryptedResponseAlg(alg); + } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ENC)) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setIdTokenEncryptedResponseEnc(alg); + } else if (name.equals(TOKEN_ENDPOINT_AUTH_SIGNING_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setTokenEndpointAuthSigningAlg(alg); + } else if (name.equals(DEFAULT_MAX_AGE)) { + client.setDefaultMaxAge(reader.nextInt()); + } else if (name.equals(REQUIRE_AUTH_TIME)) { + client.setRequireAuthTime(reader.nextBoolean()); + } else if (name.equals(DEFAULT_ACR_VALUES)) { + Set defaultACRvalues = readSet(reader); + client.setDefaultACRvalues(defaultACRvalues); + } else if (name.equals("initiateLoginUri")) { + client.setInitiateLoginUri(reader.nextString()); + } else if (name.equals(POST_LOGOUT_REDIRECT_URI)) { + Set postLogoutUris = readSet(reader); + client.setPostLogoutRedirectUris(postLogoutUris); + } else if (name.equals(REQUEST_URIS)) { + Set requestUris = readSet(reader); + client.setRequestUris(requestUris); + } else if (name.equals(DESCRIPTION)) { + client.setClientDescription(reader.nextString()); + } else if (name.equals(ALLOW_INTROSPECTION)) { + client.setAllowIntrospection(reader.nextBoolean()); + } else if (name.equals(REUSE_REFRESH_TOKEN)) { + client.setReuseRefreshToken(reader.nextBoolean()); + } else if (name.equals(CLEAR_ACCESS_TOKENS_ON_REFRESH)) { + client.setClearAccessTokensOnRefresh(reader.nextBoolean()); + } else if (name.equals(DYNAMICALLY_REGISTERED)) { + client.setDynamicallyRegistered(reader.nextBoolean()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); } - client.setAuthorities(authorities); - } else if (name.equals(ACCESS_TOKEN_VALIDITY_SECONDS)) { - client.setAccessTokenValiditySeconds(reader.nextInt()); - } else if (name.equals(REFRESH_TOKEN_VALIDITY_SECONDS)) { - client.setRefreshTokenValiditySeconds(reader.nextInt()); - } else if (name.equals(REDIRECT_URIS)) { - Set redirectUris = readSet(reader); - client.setRedirectUris(redirectUris); - } else if (name.equals(NAME)) { - client.setClientName(reader.nextString()); - } else if (name.equals(URI)) { - client.setClientUri(reader.nextString()); - } else if (name.equals(LOGO_URI)) { - client.setLogoUri(reader.nextString()); - } else if (name.equals(CONTACTS)) { - Set contacts = readSet(reader); - client.setContacts(contacts); - } else if (name.equals(TOS_URI)) { - client.setTosUri(reader.nextString()); - } else if (name.equals(TOKEN_ENDPOINT_AUTH_METHOD)) { - AuthMethod am = AuthMethod.getByValue(reader.nextString()); - client.setTokenEndpointAuthMethod(am); - } else if (name.equals(GRANT_TYPES)) { - Set grantTypes = readSet(reader); - client.setGrantTypes(grantTypes); - } else if (name.equals(RESPONSE_TYPES)) { - Set responseTypes = readSet(reader); - client.setResponseTypes(responseTypes); - } else if (name.equals(POLICY_URI)) { - client.setPolicyUri(reader.nextString()); - } else if (name.equals(APPLICATION_TYPE)) { - AppType appType = AppType.getByValue(reader.nextString()); - client.setApplicationType(appType); - } else if (name.equals(SECTOR_IDENTIFIER_URI)) { - client.setSectorIdentifierUri(reader.nextString()); - } else if (name.equals(SUBJECT_TYPE)) { - SubjectType st = SubjectType.getByValue(reader.nextString()); - client.setSubjectType(st); - } else if (name.equals(JWKS_URI)) { - client.setJwksUri(reader.nextString()); - } else if (name.equals(JWKS)) { - try { - client.setJwks(JWKSet.parse(reader.nextString())); - } catch (ParseException e) { - logger.error("Couldn't parse JWK Set", e); - } - } else if (name.equals(REQUEST_OBJECT_SIGNING_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setRequestObjectSigningAlg(alg); - } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ALG)) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setUserInfoEncryptedResponseAlg(alg); - } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ENC)) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setUserInfoEncryptedResponseEnc(alg); - } else if (name.equals(USER_INFO_SIGNED_RESPONSE_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setUserInfoSignedResponseAlg(alg); - } else if (name.equals(ID_TOKEN_SIGNED_RESPONSE_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setIdTokenSignedResponseAlg(alg); - } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ALG)) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setIdTokenEncryptedResponseAlg(alg); - } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ENC)) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setIdTokenEncryptedResponseEnc(alg); - } else if (name.equals(TOKEN_ENDPOINT_AUTH_SIGNING_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setTokenEndpointAuthSigningAlg(alg); - } else if (name.equals(DEFAULT_MAX_AGE)) { - client.setDefaultMaxAge(reader.nextInt()); - } else if (name.equals(REQUIRE_AUTH_TIME)) { - client.setRequireAuthTime(reader.nextBoolean()); - } else if (name.equals(DEFAULT_ACR_VALUES)) { - Set defaultACRvalues = readSet(reader); - client.setDefaultACRvalues(defaultACRvalues); - } else if (name.equals("initiateLoginUri")) { - client.setInitiateLoginUri(reader.nextString()); - } else if (name.equals(POST_LOGOUT_REDIRECT_URI)) { - Set postLogoutUris = readSet(reader); - client.setPostLogoutRedirectUris(postLogoutUris); - } else if (name.equals(REQUEST_URIS)) { - Set requestUris = readSet(reader); - client.setRequestUris(requestUris); - } else if (name.equals(DESCRIPTION)) { - client.setClientDescription(reader.nextString()); - } else if (name.equals(ALLOW_INTROSPECTION)) { - client.setAllowIntrospection(reader.nextBoolean()); - } else if (name.equals(REUSE_REFRESH_TOKEN)) { - client.setReuseRefreshToken(reader.nextBoolean()); - } else if (name.equals(CLEAR_ACCESS_TOKENS_ON_REFRESH)) { - client.setClearAccessTokensOnRefresh(reader.nextBoolean()); - } else if (name.equals(DYNAMICALLY_REGISTERED)) { - client.setDynamicallyRegistered(reader.nextBoolean()); - } else { + break; + default: logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -1153,35 +797,35 @@ private void readSystemScopes(JsonReader reader) throws IOException { reader.beginObject(); while (reader.hasNext()) { switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(VALUE)) { - scope.setValue(reader.nextString()); - } else if (name.equals(DESCRIPTION)) { - scope.setDescription(reader.nextString()); - } else if (name.equals(RESTRICTED)) { - scope.setRestricted(reader.nextBoolean()); - } else if (name.equals(DEFAULT_SCOPE)) { - scope.setDefaultScope(reader.nextBoolean()); - } else if (name.equals(ICON)) { - scope.setIcon(reader.nextString()); - } else if (name.equals(STRUCTURED)) { - scope.setStructured(reader.nextBoolean()); - } else if (name.equals(STRUCTURED_PARAMETER)) { - scope.setStructuredParamDescription(reader.nextString()); - } else { - logger.debug("found unexpected entry"); + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(VALUE)) { + scope.setValue(reader.nextString()); + } else if (name.equals(DESCRIPTION)) { + scope.setDescription(reader.nextString()); + } else if (name.equals(RESTRICTED)) { + scope.setRestricted(reader.nextBoolean()); + } else if (name.equals(DEFAULT_SCOPE)) { + scope.setDefaultScope(reader.nextBoolean()); + } else if (name.equals(ICON)) { + scope.setIcon(reader.nextString()); + } else if (name.equals(STRUCTURED)) { + logger.warn("Found a structured scope, ignoring structure"); + } else if (name.equals(STRUCTURED_PARAMETER)) { + logger.warn("Found a structured scope, ignoring structure"); + } else { + logger.debug("found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; + continue; } } reader.endObject(); @@ -1193,80 +837,65 @@ private void readSystemScopes(JsonReader reader) throws IOException { private void fixObjectReferences() { logger.info("Fixing object references..."); - for (Long oldRefreshTokenId : refreshTokenToClientRefs.keySet()) { - String clientRef = refreshTokenToClientRefs.get(oldRefreshTokenId); + for (Long oldRefreshTokenId : maps.getRefreshTokenToClientRefs().keySet()) { + String clientRef = maps.getRefreshTokenToClientRefs().get(oldRefreshTokenId); ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); refreshToken.setClient(client); tokenRepository.saveRefreshToken(refreshToken); } - refreshTokenToClientRefs.clear(); - for (Long oldRefreshTokenId : refreshTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = refreshTokenToAuthHolderRefs.get(oldRefreshTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); + for (Long oldRefreshTokenId : maps.getRefreshTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getRefreshTokenToAuthHolderRefs().get(oldRefreshTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); refreshToken.setAuthenticationHolder(authHolder); tokenRepository.saveRefreshToken(refreshToken); } - refreshTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToClientRefs.keySet()) { - String clientRef = accessTokenToClientRefs.get(oldAccessTokenId); + for (Long oldAccessTokenId : maps.getAccessTokenToClientRefs().keySet()) { + String clientRef = maps.getAccessTokenToClientRefs().get(oldAccessTokenId); ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setClient(client); tokenRepository.saveAccessToken(accessToken); } - accessTokenToClientRefs.clear(); - for (Long oldAccessTokenId : accessTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = accessTokenToAuthHolderRefs.get(oldAccessTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); + for (Long oldAccessTokenId : maps.getAccessTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getAccessTokenToAuthHolderRefs().get(oldAccessTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setAuthenticationHolder(authHolder); tokenRepository.saveAccessToken(accessToken); } - accessTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToRefreshTokenRefs.keySet()) { - Long oldRefreshTokenId = accessTokenToRefreshTokenRefs.get(oldAccessTokenId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); + for (Long oldAccessTokenId : maps.getAccessTokenToRefreshTokenRefs().keySet()) { + Long oldRefreshTokenId = maps.getAccessTokenToRefreshTokenRefs().get(oldAccessTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); accessToken.setRefreshToken(refreshToken); tokenRepository.saveAccessToken(accessToken); } - accessTokenToRefreshTokenRefs.clear(); - refreshTokenOldToNewIdMap.clear(); - for (Long oldAccessTokenId : accessTokenToIdTokenRefs.keySet()) { - Long oldIdTokenId = accessTokenToIdTokenRefs.get(oldAccessTokenId); - Long newIdTokenId = accessTokenOldToNewIdMap.get(oldIdTokenId); - OAuth2AccessTokenEntity idToken = tokenRepository.getAccessTokenById(newIdTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setIdToken(idToken); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToIdTokenRefs.clear(); - for (Long oldGrantId : grantToAccessTokensRefs.keySet()) { - Set oldAccessTokenIds = grantToAccessTokensRefs.get(oldGrantId); - Set tokens = new HashSet(); + for (Long oldGrantId : maps.getGrantToAccessTokensRefs().keySet()) { + Set oldAccessTokenIds = maps.getGrantToAccessTokensRefs().get(oldGrantId); + + Long newGrantId = maps.getGrantOldToNewIdMap().get(oldGrantId); + ApprovedSite site = approvedSiteRepository.getById(newGrantId); + for(Long oldTokenId : oldAccessTokenIds) { - Long newTokenId = accessTokenOldToNewIdMap.get(oldTokenId); - tokens.add(tokenRepository.getAccessTokenById(newTokenId)); + Long newTokenId = maps.getAccessTokenOldToNewIdMap().get(oldTokenId); + OAuth2AccessTokenEntity token = tokenRepository.getAccessTokenById(newTokenId); + token.setApprovedSite(site); + tokenRepository.saveAccessToken(token); } - Long newGrantId = grantOldToNewIdMap.get(oldGrantId); - ApprovedSite site = approvedSiteRepository.getById(newGrantId); - site.setApprovedAccessTokens(tokens); + approvedSiteRepository.save(site); } - accessTokenOldToNewIdMap.clear(); - grantOldToNewIdMap.clear(); logger.info("Done fixing object references."); } - + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_3.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_3.java new file mode 100644 index 0000000000..2377306e57 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_3.java @@ -0,0 +1,1314 @@ +/******************************************************************************* + * 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.impl; + +import static org.mitre.util.JsonUtils.readMap; +import static org.mitre.util.JsonUtils.readSet; +import static org.mitre.util.JsonUtils.writeNullSafeArray; + +import java.io.IOException; +import java.io.Serializable; +import java.text.ParseException; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import org.mitre.oauth2.model.AuthenticationHolderEntity; +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.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.oauth2.model.PKCEAlgorithm; +import org.mitre.oauth2.model.SavedUserAuthentication; +import org.mitre.oauth2.model.SystemScope; +import org.mitre.oauth2.repository.AuthenticationHolderRepository; +import org.mitre.oauth2.repository.OAuth2ClientRepository; +import org.mitre.oauth2.repository.OAuth2TokenRepository; +import org.mitre.oauth2.repository.SystemScopeRepository; +import org.mitre.openid.connect.model.ApprovedSite; +import org.mitre.openid.connect.model.BlacklistedSite; +import org.mitre.openid.connect.model.WhitelistedSite; +import org.mitre.openid.connect.repository.ApprovedSiteRepository; +import org.mitre.openid.connect.repository.BlacklistedSiteRepository; +import org.mitre.openid.connect.repository.WhitelistedSiteRepository; +import org.mitre.openid.connect.service.MITREidDataService; +import org.mitre.openid.connect.service.MITREidDataServiceExtension; +import org.mitre.openid.connect.service.MITREidDataServiceMaps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +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.JWTParser; + +/** + * + * Data service to import and export MITREid 1.3 configuration. + * + * @author jricher + * @author arielak + */ +@Service +@SuppressWarnings(value = {"unchecked"}) +public class MITREidDataService_1_3 extends MITREidDataServiceSupport implements MITREidDataService { + + private static final String DEFAULT_SCOPE = "defaultScope"; + private static final String RESTRICTED = "restricted"; + private static final String ICON = "icon"; + private static final String DYNAMICALLY_REGISTERED = "dynamicallyRegistered"; + private static final String CLEAR_ACCESS_TOKENS_ON_REFRESH = "clearAccessTokensOnRefresh"; + private static final String REUSE_REFRESH_TOKEN = "reuseRefreshToken"; + private static final String ALLOW_INTROSPECTION = "allowIntrospection"; + private static final String DESCRIPTION = "description"; + private static final String REQUEST_URIS = "requestUris"; + private static final String POST_LOGOUT_REDIRECT_URI = "postLogoutRedirectUri"; + private static final String INTITATE_LOGIN_URI = "intitateLoginUri"; + private static final String DEFAULT_ACR_VALUES = "defaultACRValues"; + private static final String REQUIRE_AUTH_TIME = "requireAuthTime"; + private static final String DEFAULT_MAX_AGE = "defaultMaxAge"; + private static final String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "tokenEndpointAuthSigningAlg"; + private static final String USER_INFO_ENCRYPTED_RESPONSE_ENC = "userInfoEncryptedResponseEnc"; + private static final String USER_INFO_ENCRYPTED_RESPONSE_ALG = "userInfoEncryptedResponseAlg"; + private static final String USER_INFO_SIGNED_RESPONSE_ALG = "userInfoSignedResponseAlg"; + private static final String ID_TOKEN_ENCRYPTED_RESPONSE_ENC = "idTokenEncryptedResponseEnc"; + private static final String ID_TOKEN_ENCRYPTED_RESPONSE_ALG = "idTokenEncryptedResponseAlg"; + private static final String ID_TOKEN_SIGNED_RESPONSE_ALG = "idTokenSignedResponseAlg"; + private static final String REQUEST_OBJECT_SIGNING_ALG = "requestObjectSigningAlg"; + private static final String SUBJECT_TYPE = "subjectType"; + private static final String SECTOR_IDENTIFIER_URI = "sectorIdentifierUri"; + private static final String APPLICATION_TYPE = "applicationType"; + private static final String JWKS = "jwks"; + private static final String JWKS_URI = "jwksUri"; + private static final String POLICY_URI = "policyUri"; + private static final String GRANT_TYPES = "grantTypes"; + private static final String TOKEN_ENDPOINT_AUTH_METHOD = "tokenEndpointAuthMethod"; + private static final String TOS_URI = "tosUri"; + private static final String CONTACTS = "contacts"; + private static final String LOGO_URI = "logoUri"; + private static final String REDIRECT_URIS = "redirectUris"; + private static final String REFRESH_TOKEN_VALIDITY_SECONDS = "refreshTokenValiditySeconds"; + private static final String ACCESS_TOKEN_VALIDITY_SECONDS = "accessTokenValiditySeconds"; + private static final String ID_TOKEN_VALIDITY_SECONDS = "idTokenValiditySeconds"; + private static final String DEVICE_CODE_VALIDITY_SECONDS = "deviceCodeValiditySeconds"; + private static final String SECRET = "secret"; + private static final String URI = "uri"; + private static final String CREATOR_USER_ID = "creatorUserId"; + private static final String APPROVED_ACCESS_TOKENS = "approvedAccessTokens"; + private static final String ALLOWED_SCOPES = "allowedScopes"; + private static final String USER_ID = "userId"; + private static final String TIMEOUT_DATE = "timeoutDate"; + private static final String CREATION_DATE = "creationDate"; + private static final String ACCESS_DATE = "accessDate"; + private static final String AUTHENTICATED = "authenticated"; + private static final String SOURCE_CLASS = "sourceClass"; + private static final String NAME = "name"; + private static final String SAVED_USER_AUTHENTICATION = "savedUserAuthentication"; + private static final String EXTENSIONS = "extensions"; + private static final String RESPONSE_TYPES = "responseTypes"; + private static final String REDIRECT_URI = "redirectUri"; + private static final String APPROVED = "approved"; + private static final String AUTHORITIES = "authorities"; + private static final String RESOURCE_IDS = "resourceIds"; + private static final String REQUEST_PARAMETERS = "requestParameters"; + private static final String TYPE = "type"; + private static final String SCOPE = "scope"; + private static final String REFRESH_TOKEN_ID = "refreshTokenId"; + private static final String VALUE = "value"; + private static final String AUTHENTICATION_HOLDER_ID = "authenticationHolderId"; + private static final String CLIENT_ID = "clientId"; + private static final String EXPIRATION = "expiration"; + private static final String CLAIMS_REDIRECT_URIS = "claimsRedirectUris"; + private static final String ID = "id"; + private static final String CODE_CHALLENGE_METHOD = "codeChallengeMethod"; + private static final String SOFTWARE_STATEMENT = "softwareStatement"; + private static final String SOFTWARE_VERSION = "softwareVersion"; + private static final String SOFTWARE_ID = "softwareId"; + + /** + * Logger for this class + */ + private static final Logger logger = LoggerFactory.getLogger(MITREidDataService_1_3.class); + @Autowired + private OAuth2ClientRepository clientRepository; + @Autowired + private ApprovedSiteRepository approvedSiteRepository; + @Autowired + private WhitelistedSiteRepository wlSiteRepository; + @Autowired + private BlacklistedSiteRepository blSiteRepository; + @Autowired + private AuthenticationHolderRepository authHolderRepository; + @Autowired + private OAuth2TokenRepository tokenRepository; + @Autowired + private SystemScopeRepository sysScopeRepository; + @Autowired(required = false) + private List extensions = Collections.emptyList(); + + private static final String THIS_VERSION = MITREID_CONNECT_1_3; + + private MITREidDataServiceMaps maps = new MITREidDataServiceMaps(); + + @Override + public boolean supportsVersion(String version) { + return THIS_VERSION.equals(version); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataService#export(com.google.gson.stream.JsonWriter) + */ + @Override + public void exportData(JsonWriter writer) throws IOException { + + // version tag at the root + writer.name(THIS_VERSION); + + writer.beginObject(); + + // clients list + writer.name(CLIENTS); + writer.beginArray(); + writeClients(writer); + writer.endArray(); + + writer.name(GRANTS); + writer.beginArray(); + writeGrants(writer); + writer.endArray(); + + writer.name(WHITELISTEDSITES); + writer.beginArray(); + writeWhitelistedSites(writer); + writer.endArray(); + + writer.name(BLACKLISTEDSITES); + writer.beginArray(); + writeBlacklistedSites(writer); + writer.endArray(); + + writer.name(AUTHENTICATIONHOLDERS); + writer.beginArray(); + writeAuthenticationHolders(writer); + writer.endArray(); + + writer.name(ACCESSTOKENS); + writer.beginArray(); + writeAccessTokens(writer); + writer.endArray(); + + writer.name(REFRESHTOKENS); + writer.beginArray(); + writeRefreshTokens(writer); + writer.endArray(); + + writer.name(SYSTEMSCOPES); + writer.beginArray(); + writeSystemScopes(writer); + writer.endArray(); + + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.exportExtensionData(writer); + break; + } + } + + writer.endObject(); // end mitreid-connect-1.3 + } + + /** + * @param writer + */ + private void writeRefreshTokens(JsonWriter writer) throws IOException { + for (OAuth2RefreshTokenEntity token : tokenRepository.getAllRefreshTokens()) { + writer.beginObject(); + writer.name(ID).value(token.getId()); + writer.name(EXPIRATION).value(toUTCString(token.getExpiration())); + writer.name(CLIENT_ID) + .value((token.getClient() != null) ? token.getClient().getClientId() : null); + writer.name(AUTHENTICATION_HOLDER_ID) + .value((token.getAuthenticationHolder() != null) ? token.getAuthenticationHolder().getId() : null); + writer.name(VALUE).value(token.getValue()); + writer.endObject(); + logger.debug("Wrote refresh token {}", token.getId()); + } + logger.info("Done writing refresh tokens"); + } + + /** + * @param writer + */ + private void writeAccessTokens(JsonWriter writer) throws IOException { + for (OAuth2AccessTokenEntity token : tokenRepository.getAllAccessTokens()) { + writer.beginObject(); + writer.name(ID).value(token.getId()); + writer.name(EXPIRATION).value(toUTCString(token.getExpiration())); + writer.name(CLIENT_ID) + .value((token.getClient() != null) ? token.getClient().getClientId() : null); + writer.name(AUTHENTICATION_HOLDER_ID) + .value((token.getAuthenticationHolder() != null) ? token.getAuthenticationHolder().getId() : null); + writer.name(REFRESH_TOKEN_ID) + .value((token.getRefreshToken() != null) ? token.getRefreshToken().getId() : null); + writer.name(SCOPE); + writer.beginArray(); + for (String s : token.getScope()) { + writer.value(s); + } + writer.endArray(); + writer.name(TYPE).value(token.getTokenType()); + writer.name(VALUE).value(token.getValue()); + writer.endObject(); + logger.debug("Wrote access token {}", token.getId()); + } + logger.info("Done writing access tokens"); + } + + /** + * @param writer + */ + private void writeAuthenticationHolders(JsonWriter writer) throws IOException { + for (AuthenticationHolderEntity holder : authHolderRepository.getAll()) { + writer.beginObject(); + writer.name(ID).value(holder.getId()); + + writer.name(REQUEST_PARAMETERS); + writer.beginObject(); + for (Entry entry : holder.getRequestParameters().entrySet()) { + writer.name(entry.getKey()).value(entry.getValue()); + } + writer.endObject(); + writer.name(CLIENT_ID).value(holder.getClientId()); + Set scope = holder.getScope(); + writer.name(SCOPE); + writer.beginArray(); + for (String s : scope) { + writer.value(s); + } + writer.endArray(); + writer.name(RESOURCE_IDS); + writer.beginArray(); + if (holder.getResourceIds() != null) { + for (String s : holder.getResourceIds()) { + writer.value(s); + } + } + writer.endArray(); + writer.name(AUTHORITIES); + writer.beginArray(); + for (GrantedAuthority authority : holder.getAuthorities()) { + writer.value(authority.getAuthority()); + } + writer.endArray(); + writer.name(APPROVED).value(holder.isApproved()); + writer.name(REDIRECT_URI).value(holder.getRedirectUri()); + writer.name(RESPONSE_TYPES); + writer.beginArray(); + for (String s : holder.getResponseTypes()) { + writer.value(s); + } + writer.endArray(); + writer.name(EXTENSIONS); + writer.beginObject(); + for (Entry entry : holder.getExtensions().entrySet()) { + // while the extension map itself is Serializable, we enforce storage of Strings + if (entry.getValue() instanceof String) { + writer.name(entry.getKey()).value((String) entry.getValue()); + } else { + logger.warn("Skipping non-string extension: " + entry); + } + } + writer.endObject(); + + writer.name(SAVED_USER_AUTHENTICATION); + if (holder.getUserAuth() != null) { + writer.beginObject(); + writer.name(NAME).value(holder.getUserAuth().getName()); + writer.name(SOURCE_CLASS).value(holder.getUserAuth().getSourceClass()); + writer.name(AUTHENTICATED).value(holder.getUserAuth().isAuthenticated()); + writer.name(AUTHORITIES); + writer.beginArray(); + for (GrantedAuthority authority : holder.getUserAuth().getAuthorities()) { + writer.value(authority.getAuthority()); + } + writer.endArray(); + + writer.endObject(); + } else { + writer.nullValue(); + } + + + writer.endObject(); + logger.debug("Wrote authentication holder {}", holder.getId()); + } + logger.info("Done writing authentication holders"); + } + + /** + * @param writer + */ + private void writeGrants(JsonWriter writer) throws IOException { + for (ApprovedSite site : approvedSiteRepository.getAll()) { + writer.beginObject(); + writer.name(ID).value(site.getId()); + writer.name(ACCESS_DATE).value(toUTCString(site.getAccessDate())); + writer.name(CLIENT_ID).value(site.getClientId()); + writer.name(CREATION_DATE).value(toUTCString(site.getCreationDate())); + writer.name(TIMEOUT_DATE).value(toUTCString(site.getTimeoutDate())); + writer.name(USER_ID).value(site.getUserId()); + writer.name(ALLOWED_SCOPES); + writeNullSafeArray(writer, site.getAllowedScopes()); + List tokens = tokenRepository.getAccessTokensForApprovedSite(site); + writer.name(APPROVED_ACCESS_TOKENS); + writer.beginArray(); + for (OAuth2AccessTokenEntity token : tokens) { + writer.value(token.getId()); + } + writer.endArray(); + writer.endObject(); + logger.debug("Wrote grant {}", site.getId()); + } + logger.info("Done writing grants"); + } + + /** + * @param writer + */ + private void writeWhitelistedSites(JsonWriter writer) throws IOException { + for (WhitelistedSite wlSite : wlSiteRepository.getAll()) { + writer.beginObject(); + writer.name(ID).value(wlSite.getId()); + writer.name(CLIENT_ID).value(wlSite.getClientId()); + writer.name(CREATOR_USER_ID).value(wlSite.getCreatorUserId()); + writer.name(ALLOWED_SCOPES); + writeNullSafeArray(writer, wlSite.getAllowedScopes()); + writer.endObject(); + logger.debug("Wrote whitelisted site {}", wlSite.getId()); + } + logger.info("Done writing whitelisted sites"); + } + + /** + * @param writer + */ + private void writeBlacklistedSites(JsonWriter writer) throws IOException { + for (BlacklistedSite blSite : blSiteRepository.getAll()) { + writer.beginObject(); + writer.name(ID).value(blSite.getId()); + writer.name(URI).value(blSite.getUri()); + writer.endObject(); + logger.debug("Wrote blacklisted site {}", blSite.getId()); + } + logger.info("Done writing blacklisted sites"); + } + + /** + * @param writer + */ + private void writeClients(JsonWriter writer) { + for (ClientDetailsEntity client : clientRepository.getAllClients()) { + try { + writer.beginObject(); + writer.name(CLIENT_ID).value(client.getClientId()); + writer.name(RESOURCE_IDS); + writeNullSafeArray(writer, client.getResourceIds()); + + writer.name(SECRET).value(client.getClientSecret()); + + writer.name(SCOPE); + writeNullSafeArray(writer, client.getScope()); + + writer.name(AUTHORITIES); + writer.beginArray(); + for (GrantedAuthority authority : client.getAuthorities()) { + writer.value(authority.getAuthority()); + } + writer.endArray(); + writer.name(ACCESS_TOKEN_VALIDITY_SECONDS).value(client.getAccessTokenValiditySeconds()); + writer.name(REFRESH_TOKEN_VALIDITY_SECONDS).value(client.getRefreshTokenValiditySeconds()); + writer.name(ID_TOKEN_VALIDITY_SECONDS).value(client.getIdTokenValiditySeconds()); + writer.name(DEVICE_CODE_VALIDITY_SECONDS).value(client.getDeviceCodeValiditySeconds()); + writer.name(REDIRECT_URIS); + writeNullSafeArray(writer, client.getRedirectUris()); + writer.name(CLAIMS_REDIRECT_URIS); + writeNullSafeArray(writer, client.getClaimsRedirectUris()); + writer.name(NAME).value(client.getClientName()); + writer.name(URI).value(client.getClientUri()); + writer.name(LOGO_URI).value(client.getLogoUri()); + writer.name(CONTACTS); + writeNullSafeArray(writer, client.getContacts()); + writer.name(TOS_URI).value(client.getTosUri()); + writer.name(TOKEN_ENDPOINT_AUTH_METHOD) + .value((client.getTokenEndpointAuthMethod() != null) ? client.getTokenEndpointAuthMethod().getValue() : null); + writer.name(GRANT_TYPES); + writer.beginArray(); + for (String s : client.getGrantTypes()) { + writer.value(s); + } + writer.endArray(); + writer.name(RESPONSE_TYPES); + writer.beginArray(); + for (String s : client.getResponseTypes()) { + writer.value(s); + } + writer.endArray(); + writer.name(POLICY_URI).value(client.getPolicyUri()); + writer.name(JWKS_URI).value(client.getJwksUri()); + writer.name(JWKS).value((client.getJwks() != null) ? client.getJwks().toString() : null); + writer.name(APPLICATION_TYPE) + .value((client.getApplicationType() != null) ? client.getApplicationType().getValue() : null); + writer.name(SECTOR_IDENTIFIER_URI).value(client.getSectorIdentifierUri()); + writer.name(SUBJECT_TYPE) + .value((client.getSubjectType() != null) ? client.getSubjectType().getValue() : null); + writer.name(REQUEST_OBJECT_SIGNING_ALG) + .value((client.getRequestObjectSigningAlg() != null) ? client.getRequestObjectSigningAlg().getName() : null); + writer.name(ID_TOKEN_SIGNED_RESPONSE_ALG) + .value((client.getIdTokenSignedResponseAlg() != null) ? client.getIdTokenSignedResponseAlg().getName() : null); + writer.name(ID_TOKEN_ENCRYPTED_RESPONSE_ALG) + .value((client.getIdTokenEncryptedResponseAlg() != null) ? client.getIdTokenEncryptedResponseAlg().getName() : null); + writer.name(ID_TOKEN_ENCRYPTED_RESPONSE_ENC) + .value((client.getIdTokenEncryptedResponseEnc() != null) ? client.getIdTokenEncryptedResponseEnc().getName() : null); + writer.name(USER_INFO_SIGNED_RESPONSE_ALG) + .value((client.getUserInfoSignedResponseAlg() != null) ? client.getUserInfoSignedResponseAlg().getName() : null); + writer.name(USER_INFO_ENCRYPTED_RESPONSE_ALG) + .value((client.getUserInfoEncryptedResponseAlg() != null) ? client.getUserInfoEncryptedResponseAlg().getName() : null); + writer.name(USER_INFO_ENCRYPTED_RESPONSE_ENC) + .value((client.getUserInfoEncryptedResponseEnc() != null) ? client.getUserInfoEncryptedResponseEnc().getName() : null); + writer.name(TOKEN_ENDPOINT_AUTH_SIGNING_ALG) + .value((client.getTokenEndpointAuthSigningAlg() != null) ? client.getTokenEndpointAuthSigningAlg().getName() : null); + writer.name(DEFAULT_MAX_AGE).value(client.getDefaultMaxAge()); + Boolean requireAuthTime = null; + try { + requireAuthTime = client.getRequireAuthTime(); + } catch (NullPointerException e) { + } + if (requireAuthTime != null) { + writer.name(REQUIRE_AUTH_TIME).value(requireAuthTime); + } + writer.name(DEFAULT_ACR_VALUES); + writeNullSafeArray(writer, client.getDefaultACRvalues()); + writer.name(INTITATE_LOGIN_URI).value(client.getInitiateLoginUri()); + writer.name(POST_LOGOUT_REDIRECT_URI); + writeNullSafeArray(writer, client.getPostLogoutRedirectUris()); + writer.name(REQUEST_URIS); + writeNullSafeArray(writer, client.getRequestUris()); + writer.name(DESCRIPTION).value(client.getClientDescription()); + writer.name(ALLOW_INTROSPECTION).value(client.isAllowIntrospection()); + writer.name(REUSE_REFRESH_TOKEN).value(client.isReuseRefreshToken()); + writer.name(CLEAR_ACCESS_TOKENS_ON_REFRESH).value(client.isClearAccessTokensOnRefresh()); + writer.name(DYNAMICALLY_REGISTERED).value(client.isDynamicallyRegistered()); + writer.name(CODE_CHALLENGE_METHOD).value(client.getCodeChallengeMethod() != null ? client.getCodeChallengeMethod().getName() : null); + writer.name(SOFTWARE_ID).value(client.getSoftwareId()); + writer.name(SOFTWARE_VERSION).value(client.getSoftwareVersion()); + writer.name(SOFTWARE_STATEMENT).value(client.getSoftwareStatement() != null ? client.getSoftwareStatement().serialize() : null); + writer.name(CREATION_DATE).value(toUTCString(client.getCreatedAt())); + writer.endObject(); + logger.debug("Wrote client {}", client.getId()); + } catch (IOException ex) { + logger.error("Unable to write client {}", client.getId(), ex); + } + } + logger.info("Done writing clients"); + } + + /** + * @param writer + */ + private void writeSystemScopes(JsonWriter writer) { + for (SystemScope sysScope : sysScopeRepository.getAll()) { + try { + writer.beginObject(); + writer.name(ID).value(sysScope.getId()); + writer.name(DESCRIPTION).value(sysScope.getDescription()); + writer.name(ICON).value(sysScope.getIcon()); + writer.name(VALUE).value(sysScope.getValue()); + writer.name(RESTRICTED).value(sysScope.isRestricted()); + writer.name(DEFAULT_SCOPE).value(sysScope.isDefaultScope()); + writer.endObject(); + logger.debug("Wrote system scope {}", sysScope.getId()); + } catch (IOException ex) { + logger.error("Unable to write system scope {}", sysScope.getId(), ex); + } + } + logger.info("Done writing system scopes"); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataService#importData(com.google.gson.stream.JsonReader) + */ + @Override + public void importData(JsonReader reader) throws IOException { + + logger.info("Reading configuration for 1.3"); + + // this *HAS* to start as an object + reader.beginObject(); + + while (reader.hasNext()) { + JsonToken tok = reader.peek(); + switch (tok) { + case NAME: + String name = reader.nextName(); + // find out which member it is + if (name.equals(CLIENTS)) { + readClients(reader); + } else if (name.equals(GRANTS)) { + readGrants(reader); + } else if (name.equals(WHITELISTEDSITES)) { + readWhitelistedSites(reader); + } else if (name.equals(BLACKLISTEDSITES)) { + readBlacklistedSites(reader); + } else if (name.equals(AUTHENTICATIONHOLDERS)) { + readAuthenticationHolders(reader); + } else if (name.equals(ACCESSTOKENS)) { + readAccessTokens(reader); + } else if (name.equals(REFRESHTOKENS)) { + readRefreshTokens(reader); + } else if (name.equals(SYSTEMSCOPES)) { + readSystemScopes(reader); + } else { + boolean processed = false; + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + processed = extension.importExtensionData(name, reader); + if (processed) { + // if the extension processed data, break out of this inner loop + // (only the first extension to claim an extension point gets it) + break; + } + } + } + if (!processed) { + // unknown token, skip it + reader.skipValue(); + } + } + break; + case END_OBJECT: + // the object ended, we're done here + reader.endObject(); + continue; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + fixObjectReferences(); + for (MITREidDataServiceExtension extension : extensions) { + if (extension.supportsVersion(THIS_VERSION)) { + extension.fixExtensionObjectReferences(maps); + break; + } + } + maps.clearAll(); + } + + /** + * @param reader + * @throws IOException + */ + /** + * @param reader + * @throws IOException + */ + private void readRefreshTokens(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + OAuth2RefreshTokenEntity token = new OAuth2RefreshTokenEntity(); + reader.beginObject(); + Long currentId = null; + String clientId = null; + Long authHolderId = null; + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(EXPIRATION)) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals(VALUE)) { + String value = reader.nextString(); + try { + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals(CLIENT_ID)) { + clientId = reader.nextString(); + } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { + authHolderId = reader.nextLong(); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Long newId = tokenRepository.saveRefreshToken(token).getId(); + maps.getRefreshTokenToClientRefs().put(currentId, clientId); + maps.getRefreshTokenToAuthHolderRefs().put(currentId, authHolderId); + maps.getRefreshTokenOldToNewIdMap().put(currentId, newId); + logger.debug("Read refresh token {}", currentId); + } + reader.endArray(); + logger.info("Done reading refresh tokens"); + } + /** + * @param reader + * @throws IOException + */ + /** + * @param reader + * @throws IOException + */ + private void readAccessTokens(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); + reader.beginObject(); + Long currentId = null; + String clientId = null; + Long authHolderId = null; + Long refreshTokenId = null; + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(EXPIRATION)) { + Date date = utcToDate(reader.nextString()); + token.setExpiration(date); + } else if (name.equals(VALUE)) { + String value = reader.nextString(); + try { + // all tokens are JWTs + token.setJwt(JWTParser.parse(value)); + } catch (ParseException ex) { + logger.error("Unable to set refresh token value to {}", value, ex); + } + } else if (name.equals(CLIENT_ID)) { + clientId = reader.nextString(); + } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { + authHolderId = reader.nextLong(); + } else if (name.equals(REFRESH_TOKEN_ID)) { + refreshTokenId = reader.nextLong(); + } else if (name.equals(SCOPE)) { + Set scope = readSet(reader); + token.setScope(scope); + } else if (name.equals(TYPE)) { + token.setTokenType(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Long newId = tokenRepository.saveAccessToken(token).getId(); + maps.getAccessTokenToClientRefs().put(currentId, clientId); + maps.getAccessTokenToAuthHolderRefs().put(currentId, authHolderId); + if (refreshTokenId != null) { + maps.getAccessTokenToRefreshTokenRefs().put(currentId, refreshTokenId); + } + maps.getAccessTokenOldToNewIdMap().put(currentId, newId); + logger.debug("Read access token {}", currentId); + } + reader.endArray(); + logger.info("Done reading access tokens"); + } + /** + * @param reader + * @throws IOException + */ + private void readAuthenticationHolders(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + AuthenticationHolderEntity ahe = new AuthenticationHolderEntity(); + reader.beginObject(); + Long currentId = null; + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(REQUEST_PARAMETERS)) { + ahe.setRequestParameters(readMap(reader)); + } else if (name.equals(CLIENT_ID)) { + ahe.setClientId(reader.nextString()); + } else if (name.equals(SCOPE)) { + ahe.setScope(readSet(reader)); + } else if (name.equals(RESOURCE_IDS)) { + ahe.setResourceIds(readSet(reader)); + } else if (name.equals(AUTHORITIES)) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + ahe.setAuthorities(authorities); + } else if (name.equals(APPROVED)) { + ahe.setApproved(reader.nextBoolean()); + } else if (name.equals(REDIRECT_URI)) { + ahe.setRedirectUri(reader.nextString()); + } else if (name.equals(RESPONSE_TYPES)) { + ahe.setResponseTypes(readSet(reader)); + } else if (name.equals(EXTENSIONS)) { + ahe.setExtensions(readMap(reader)); + } else if (name.equals(SAVED_USER_AUTHENTICATION)) { + ahe.setUserAuth(readSavedUserAuthentication(reader)); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Long newId = authHolderRepository.save(ahe).getId(); + maps.getAuthHolderOldToNewIdMap().put(currentId, newId); + logger.debug("Read authentication holder {}", currentId); + } + reader.endArray(); + logger.info("Done reading authentication holders"); + } + + /** + * @param reader + * @return + * @throws IOException + */ + private SavedUserAuthentication readSavedUserAuthentication(JsonReader reader) throws IOException { + SavedUserAuthentication savedUserAuth = new SavedUserAuthentication(); + reader.beginObject(); + + while (reader.hasNext()) { + switch(reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(NAME)) { + savedUserAuth.setName(reader.nextString()); + } else if (name.equals(SOURCE_CLASS)) { + savedUserAuth.setSourceClass(reader.nextString()); + } else if (name.equals(AUTHENTICATED)) { + savedUserAuth.setAuthenticated(reader.nextBoolean()); + } else if (name.equals(AUTHORITIES)) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + savedUserAuth.setAuthorities(authorities); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + + reader.endObject(); + return savedUserAuth; + } + + /** + * @param reader + * @throws IOException + */ + private void readGrants(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + ApprovedSite site = new ApprovedSite(); + Long currentId = null; + Set tokenIds = null; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(ACCESS_DATE)) { + Date date = utcToDate(reader.nextString()); + site.setAccessDate(date); + } else if (name.equals(CLIENT_ID)) { + site.setClientId(reader.nextString()); + } else if (name.equals(CREATION_DATE)) { + Date date = utcToDate(reader.nextString()); + site.setCreationDate(date); + } else if (name.equals(TIMEOUT_DATE)) { + Date date = utcToDate(reader.nextString()); + site.setTimeoutDate(date); + } else if (name.equals(USER_ID)) { + site.setUserId(reader.nextString()); + } else if (name.equals(ALLOWED_SCOPES)) { + Set allowedScopes = readSet(reader); + site.setAllowedScopes(allowedScopes); + } else if (name.equals(APPROVED_ACCESS_TOKENS)) { + tokenIds = readSet(reader); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Long newId = approvedSiteRepository.save(site).getId(); + maps.getGrantOldToNewIdMap().put(currentId, newId); + if (tokenIds != null) { + maps.getGrantToAccessTokensRefs().put(currentId, tokenIds); + } + logger.debug("Read grant {}", currentId); + } + reader.endArray(); + logger.info("Done reading grants"); + } + + /** + * @param reader + * @throws IOException + */ + private void readWhitelistedSites(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + WhitelistedSite wlSite = new WhitelistedSite(); + Long currentId = null; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals(ID)) { + currentId = reader.nextLong(); + } else if (name.equals(CLIENT_ID)) { + wlSite.setClientId(reader.nextString()); + } else if (name.equals(CREATOR_USER_ID)) { + wlSite.setCreatorUserId(reader.nextString()); + } else if (name.equals(ALLOWED_SCOPES)) { + Set allowedScopes = readSet(reader); + wlSite.setAllowedScopes(allowedScopes); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Long newId = wlSiteRepository.save(wlSite).getId(); + maps.getWhitelistedSiteOldToNewIdMap().put(currentId, newId); + } + reader.endArray(); + logger.info("Done reading whitelisted sites"); + } + + /** + * @param reader + * @throws IOException + */ + private void readBlacklistedSites(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + BlacklistedSite blSite = new BlacklistedSite(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals(ID)) { + reader.skipValue(); + } else if (name.equals(URI)) { + blSite.setUri(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + blSiteRepository.save(blSite); + } + reader.endArray(); + logger.info("Done reading blacklisted sites"); + } + + /** + * @param reader + * @throws IOException + */ + private void readClients(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + ClientDetailsEntity client = new ClientDetailsEntity(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(CLIENT_ID)) { + client.setClientId(reader.nextString()); + } else if (name.equals(RESOURCE_IDS)) { + Set resourceIds = readSet(reader); + client.setResourceIds(resourceIds); + } else if (name.equals(SECRET)) { + client.setClientSecret(reader.nextString()); + } else if (name.equals(SCOPE)) { + Set scope = readSet(reader); + client.setScope(scope); + } else if (name.equals(AUTHORITIES)) { + Set authorityStrs = readSet(reader); + Set authorities = new HashSet(); + for (String s : authorityStrs) { + GrantedAuthority ga = new SimpleGrantedAuthority(s); + authorities.add(ga); + } + client.setAuthorities(authorities); + } else if (name.equals(ACCESS_TOKEN_VALIDITY_SECONDS)) { + client.setAccessTokenValiditySeconds(reader.nextInt()); + } else if (name.equals(REFRESH_TOKEN_VALIDITY_SECONDS)) { + client.setRefreshTokenValiditySeconds(reader.nextInt()); + } else if (name.equals(ID_TOKEN_VALIDITY_SECONDS)) { + client.setIdTokenValiditySeconds(reader.nextInt()); + } else if (name.equals(DEVICE_CODE_VALIDITY_SECONDS)) { + client.setDeviceCodeValiditySeconds(reader.nextInt()); + } else if (name.equals(REDIRECT_URIS)) { + Set redirectUris = readSet(reader); + client.setRedirectUris(redirectUris); + } else if (name.equals(CLAIMS_REDIRECT_URIS)) { + Set claimsRedirectUris = readSet(reader); + client.setClaimsRedirectUris(claimsRedirectUris); + } else if (name.equals(NAME)) { + client.setClientName(reader.nextString()); + } else if (name.equals(URI)) { + client.setClientUri(reader.nextString()); + } else if (name.equals(LOGO_URI)) { + client.setLogoUri(reader.nextString()); + } else if (name.equals(CONTACTS)) { + Set contacts = readSet(reader); + client.setContacts(contacts); + } else if (name.equals(TOS_URI)) { + client.setTosUri(reader.nextString()); + } else if (name.equals(TOKEN_ENDPOINT_AUTH_METHOD)) { + AuthMethod am = AuthMethod.getByValue(reader.nextString()); + client.setTokenEndpointAuthMethod(am); + } else if (name.equals(GRANT_TYPES)) { + Set grantTypes = readSet(reader); + client.setGrantTypes(grantTypes); + } else if (name.equals(RESPONSE_TYPES)) { + Set responseTypes = readSet(reader); + client.setResponseTypes(responseTypes); + } else if (name.equals(POLICY_URI)) { + client.setPolicyUri(reader.nextString()); + } else if (name.equals(APPLICATION_TYPE)) { + AppType appType = AppType.getByValue(reader.nextString()); + client.setApplicationType(appType); + } else if (name.equals(SECTOR_IDENTIFIER_URI)) { + client.setSectorIdentifierUri(reader.nextString()); + } else if (name.equals(SUBJECT_TYPE)) { + SubjectType st = SubjectType.getByValue(reader.nextString()); + client.setSubjectType(st); + } else if (name.equals(JWKS_URI)) { + client.setJwksUri(reader.nextString()); + } else if (name.equals(JWKS)) { + try { + client.setJwks(JWKSet.parse(reader.nextString())); + } catch (ParseException e) { + logger.error("Couldn't parse JWK Set", e); + } + } else if (name.equals(REQUEST_OBJECT_SIGNING_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setRequestObjectSigningAlg(alg); + } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ALG)) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setUserInfoEncryptedResponseAlg(alg); + } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ENC)) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setUserInfoEncryptedResponseEnc(alg); + } else if (name.equals(USER_INFO_SIGNED_RESPONSE_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setUserInfoSignedResponseAlg(alg); + } else if (name.equals(ID_TOKEN_SIGNED_RESPONSE_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setIdTokenSignedResponseAlg(alg); + } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ALG)) { + JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); + client.setIdTokenEncryptedResponseAlg(alg); + } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ENC)) { + EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); + client.setIdTokenEncryptedResponseEnc(alg); + } else if (name.equals(TOKEN_ENDPOINT_AUTH_SIGNING_ALG)) { + JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); + client.setTokenEndpointAuthSigningAlg(alg); + } else if (name.equals(DEFAULT_MAX_AGE)) { + client.setDefaultMaxAge(reader.nextInt()); + } else if (name.equals(REQUIRE_AUTH_TIME)) { + client.setRequireAuthTime(reader.nextBoolean()); + } else if (name.equals(DEFAULT_ACR_VALUES)) { + Set defaultACRvalues = readSet(reader); + client.setDefaultACRvalues(defaultACRvalues); + } else if (name.equals("initiateLoginUri")) { + client.setInitiateLoginUri(reader.nextString()); + } else if (name.equals(POST_LOGOUT_REDIRECT_URI)) { + Set postLogoutUris = readSet(reader); + client.setPostLogoutRedirectUris(postLogoutUris); + } else if (name.equals(REQUEST_URIS)) { + Set requestUris = readSet(reader); + client.setRequestUris(requestUris); + } else if (name.equals(DESCRIPTION)) { + client.setClientDescription(reader.nextString()); + } else if (name.equals(ALLOW_INTROSPECTION)) { + client.setAllowIntrospection(reader.nextBoolean()); + } else if (name.equals(REUSE_REFRESH_TOKEN)) { + client.setReuseRefreshToken(reader.nextBoolean()); + } else if (name.equals(CLEAR_ACCESS_TOKENS_ON_REFRESH)) { + client.setClearAccessTokensOnRefresh(reader.nextBoolean()); + } else if (name.equals(DYNAMICALLY_REGISTERED)) { + client.setDynamicallyRegistered(reader.nextBoolean()); + } else if (name.equals(CODE_CHALLENGE_METHOD)) { + client.setCodeChallengeMethod(PKCEAlgorithm.parse(reader.nextString())); + } else if (name.equals(SOFTWARE_ID)) { + client.setSoftwareId(reader.nextString()); + } else if (name.equals(SOFTWARE_VERSION)) { + client.setSoftwareVersion(reader.nextString()); + } else if (name.equals(SOFTWARE_STATEMENT)) { + try { + client.setSoftwareStatement(JWTParser.parse(reader.nextString())); + } catch (ParseException e) { + logger.error("Couldn't parse software statement", e); + } + } else if (name.equals(CREATION_DATE)) { + Date date = utcToDate(reader.nextString()); + client.setCreatedAt(date); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + clientRepository.saveClient(client); + } + reader.endArray(); + logger.info("Done reading clients"); + } + + /** + * Read the list of system scopes from the reader and insert them into the + * scope repository. + * + * @param reader + * @throws IOException + */ + private void readSystemScopes(JsonReader reader) throws IOException { + reader.beginArray(); + while (reader.hasNext()) { + SystemScope scope = new SystemScope(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(VALUE)) { + scope.setValue(reader.nextString()); + } else if (name.equals(DESCRIPTION)) { + scope.setDescription(reader.nextString()); + } else if (name.equals(RESTRICTED)) { + scope.setRestricted(reader.nextBoolean()); + } else if (name.equals(DEFAULT_SCOPE)) { + scope.setDefaultScope(reader.nextBoolean()); + } else if (name.equals(ICON)) { + scope.setIcon(reader.nextString()); + } else { + logger.debug("found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + sysScopeRepository.save(scope); + } + reader.endArray(); + logger.info("Done reading system scopes"); + } + + private void fixObjectReferences() { + logger.info("Fixing object references..."); + for (Long oldRefreshTokenId : maps.getRefreshTokenToClientRefs().keySet()) { + String clientRef = maps.getRefreshTokenToClientRefs().get(oldRefreshTokenId); + ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); + OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); + refreshToken.setClient(client); + tokenRepository.saveRefreshToken(refreshToken); + } + for (Long oldRefreshTokenId : maps.getRefreshTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getRefreshTokenToAuthHolderRefs().get(oldRefreshTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); + AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); + OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); + refreshToken.setAuthenticationHolder(authHolder); + tokenRepository.saveRefreshToken(refreshToken); + } + for (Long oldAccessTokenId : maps.getAccessTokenToClientRefs().keySet()) { + String clientRef = maps.getAccessTokenToClientRefs().get(oldAccessTokenId); + ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); + OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); + accessToken.setClient(client); + tokenRepository.saveAccessToken(accessToken); + } + for (Long oldAccessTokenId : maps.getAccessTokenToAuthHolderRefs().keySet()) { + Long oldAuthHolderId = maps.getAccessTokenToAuthHolderRefs().get(oldAccessTokenId); + Long newAuthHolderId = maps.getAuthHolderOldToNewIdMap().get(oldAuthHolderId); + AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); + OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); + accessToken.setAuthenticationHolder(authHolder); + tokenRepository.saveAccessToken(accessToken); + } + for (Long oldAccessTokenId : maps.getAccessTokenToRefreshTokenRefs().keySet()) { + Long oldRefreshTokenId = maps.getAccessTokenToRefreshTokenRefs().get(oldAccessTokenId); + Long newRefreshTokenId = maps.getRefreshTokenOldToNewIdMap().get(oldRefreshTokenId); + OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); + Long newAccessTokenId = maps.getAccessTokenOldToNewIdMap().get(oldAccessTokenId); + OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); + accessToken.setRefreshToken(refreshToken); + tokenRepository.saveAccessToken(accessToken); + } + for (Long oldGrantId : maps.getGrantToAccessTokensRefs().keySet()) { + Set oldAccessTokenIds = maps.getGrantToAccessTokensRefs().get(oldGrantId); + + Long newGrantId = maps.getGrantOldToNewIdMap().get(oldGrantId); + ApprovedSite site = approvedSiteRepository.getById(newGrantId); + + for(Long oldTokenId : oldAccessTokenIds) { + Long newTokenId = maps.getAccessTokenOldToNewIdMap().get(oldTokenId); + OAuth2AccessTokenEntity token = tokenRepository.getAccessTokenById(newTokenId); + token.setApprovedSite(site); + tokenRepository.saveAccessToken(token); + } + + approvedSiteRepository.save(site); + } + /* + refreshTokenToClientRefs.clear(); + refreshTokenToAuthHolderRefs.clear(); + accessTokenToClientRefs.clear(); + accessTokenToAuthHolderRefs.clear(); + accessTokenToRefreshTokenRefs.clear(); + refreshTokenOldToNewIdMap.clear(); + accessTokenOldToNewIdMap.clear(); + grantOldToNewIdMap.clear(); + */ + logger.info("Done fixing object references."); + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java new file mode 100644 index 0000000000..74d98ea31b --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/MatchLoginHintsAgainstUsers.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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.impl; + +import org.mitre.openid.connect.model.UserInfo; +import org.mitre.openid.connect.service.LoginHintExtracter; +import org.mitre.openid.connect.service.UserInfoService; +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.base.Strings; + +/** + * Checks the login hint against the User Info collection, only populates it if a user is found. + * @author jricher + * + */ +public class MatchLoginHintsAgainstUsers implements LoginHintExtracter { + + @Autowired + private UserInfoService userInfoService; + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.LoginHintTester#useHint(java.lang.String) + */ + @Override + public String extractHint(String loginHint) { + if (Strings.isNullOrEmpty(loginHint)) { + return null; + } else { + UserInfo user = userInfoService.getByEmailAddress(loginHint); + if (user == null) { + user = userInfoService.getByUsername(loginHint); + if (user == null) { + return null; + } else { + return user.getPreferredUsername(); + } + } else { + return user.getPreferredUsername(); + } + } + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/PassAllLoginHints.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/PassAllLoginHints.java new file mode 100644 index 0000000000..b1894466b6 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/PassAllLoginHints.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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.impl; + +import org.mitre.openid.connect.service.LoginHintExtracter; + +/** + * Sends all login hints through to the login page regardless of setup. + * + * @author jricher + * + */ +public class PassAllLoginHints implements LoginHintExtracter { + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.LoginHintTester#useHint(java.lang.String) + */ + @Override + public String extractHint(String loginHint) { + return loginHint; + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/RemoveLoginHintsWithHTTP.java new file mode 100644 index 0000000000..eab1585c20 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/RemoveLoginHintsWithHTTP.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.openid.connect.service.impl; + +import org.mitre.openid.connect.service.LoginHintExtracter; + +import com.google.common.base.Strings; + +/** + * Passes login hints that don't start with "http" + * + * @author jricher + * + */ +public class RemoveLoginHintsWithHTTP implements LoginHintExtracter { + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.LoginHintTester#useHint(java.lang.String) + */ + @Override + public String extractHint(String loginHint) { + if (Strings.isNullOrEmpty(loginHint)) { + return null; + } else { + if (loginHint.startsWith("http")) { + return null; + } else { + return loginHint; + } + } + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java index 6a6796199b..14b46fcc36 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/UUIDPairwiseIdentiferService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service.impl; diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java index 0a3ea618e8..d5b6a9cdc0 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/ConnectTokenEnhancer.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -20,15 +21,12 @@ import java.util.UUID; 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.ClientDetailsEntity; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.SystemScopeService; import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.model.UserInfo; -import org.mitre.openid.connect.service.ApprovedSiteService; import org.mitre.openid.connect.service.OIDCTokenService; import org.mitre.openid.connect.service.UserInfoService; import org.slf4j.Logger; @@ -40,10 +38,13 @@ import org.springframework.security.oauth2.provider.token.TokenEnhancer; import org.springframework.stereotype.Service; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTClaimsSet.Builder; import com.nimbusds.jwt.SignedJWT; @Service @@ -63,22 +64,12 @@ public class ConnectTokenEnhancer implements TokenEnhancer { @Autowired private ClientDetailsEntityService clientService; - @Autowired - private ApprovedSiteService approvedSiteService; - @Autowired private UserInfoService userInfoService; @Autowired private OIDCTokenService connectTokenService; - @Autowired - private JWKSetCacheService encryptors; - - @Autowired - private SymmetricKeyJWTValidatorCacheService symmetricCacheService; - - @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { @@ -88,21 +79,28 @@ public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentica String clientId = originalAuthRequest.getClientId(); ClientDetailsEntity client = clientService.loadClientByClientId(clientId); - JWTClaimsSet claims = new JWTClaimsSet(); - - claims.setAudience(Lists.newArrayList(clientId)); - - claims.setIssuer(configBean.getIssuer()); - - claims.setIssueTime(new Date()); + Builder builder = new JWTClaimsSet.Builder() + .claim("azp", clientId) + .issuer(configBean.getIssuer()) + .issueTime(new Date()) + .expirationTime(token.getExpiration()) + .subject(authentication.getName()) + .jwtID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it + + String audience = (String) authentication.getOAuth2Request().getExtensions().get("aud"); + if (!Strings.isNullOrEmpty(audience)) { + builder.audience(Lists.newArrayList(audience)); + } - claims.setExpirationTime(token.getExpiration()); + addCustomAccessTokenClaims(builder, token, authentication); - claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it + JWTClaimsSet claims = builder.build(); JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); - - SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims); + JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, + jwtService.getDefaultSignerKeyId(), + null, null); + SignedJWT signed = new SignedJWT(header, claims); jwtService.signJwt(signed); @@ -113,7 +111,7 @@ public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentica * may or may not include the scope parameter. As long as the AuthorizationRequest * has the proper scope, we can consider this a valid OpenID Connect request. Otherwise, * we consider it to be a vanilla OAuth2 request. - * + * * Also, there must be a user authentication involved in the request for it to be considered * OIDC and not OAuth, so we check for that as well. */ @@ -125,12 +123,12 @@ public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentica if (userInfo != null) { - OAuth2AccessTokenEntity idTokenEntity = connectTokenService.createIdToken(client, + JWT idToken = connectTokenService.createIdToken(client, originalAuthRequest, claims.getIssueTime(), userInfo.getSub(), token); // attach the id token to the parent access token - token.setIdToken(idTokenEntity); + token.setIdToken(idToken); } else { // can't create an id token if we can't find the user logger.warn("Request for ID token when no user is present."); @@ -164,4 +162,15 @@ public void setClientService(ClientDetailsEntityService clientService) { this.clientService = clientService; } + + /** + * Hook for subclasses that allows adding custom claims to the JWT that will be used as access token. + * @param builder the builder holding the current claims + * @param token the un-enhanced token + * @param authentication current authentication + */ + protected void addCustomAccessTokenClaims(JWTClaimsSet.Builder builder, OAuth2AccessTokenEntity token, + OAuth2Authentication authentication) { + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java index 99968536f5..821b1fafb6 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/token/TofuUserApprovalHandler.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,11 @@ *******************************************************************************/ package org.mitre.openid.connect.token; +import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_CONSENT; +import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; + import java.util.Calendar; import java.util.Collection; import java.util.Date; @@ -26,7 +32,6 @@ import javax.servlet.http.HttpSession; -import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.service.SystemScopeService; import org.mitre.openid.connect.model.ApprovedSite; import org.mitre.openid.connect.model.WhitelistedSite; @@ -47,21 +52,16 @@ import com.google.common.base.Strings; import com.google.common.collect.Sets; -import static org.mitre.openid.connect.request.ConnectRequestParameters.APPROVED_SITE; -import static org.mitre.openid.connect.request.ConnectRequestParameters.CSRF; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT; -import static org.mitre.openid.connect.request.ConnectRequestParameters.PROMPT_SEPARATOR; - /** * Custom User Approval Handler implementation which uses a concept of a whitelist, * blacklist, and greylist. - * + * * Blacklisted sites will be caught and handled before this * point. - * + * * Whitelisted sites will be automatically approved, and an ApprovedSite entry will * be created for the site the first time a given user access it. - * + * * All other sites fall into the greylist - the user will be presented with the user * approval page upon their first visit * @author aanganes @@ -85,12 +85,12 @@ public class TofuUserApprovalHandler implements UserApprovalHandler { /** * Check if the user has already stored a positive approval decision for this site; or if the * site is whitelisted, approve it automatically. - * + * * Otherwise, return false so that the user will see the approval page and can make their own decision. - * + * * @param authorizationRequest the incoming authorization request * @param userAuthentication the Principal representing the currently-logged-in user - * + * * @return true if the site is approved, false otherwise */ @Override @@ -102,21 +102,8 @@ public boolean isApproved(AuthorizationRequest authorizationRequest, Authenticat return true; } else { // if not, check to see if the user has approved it - if (Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval"))) { // TODO: make parameter name configurable? - - // check the value of the CSRF parameter - - if (authorizationRequest.getExtensions().get(CSRF) != null) { - if (authorizationRequest.getExtensions().get(CSRF).equals(authorizationRequest.getApprovalParameters().get(CSRF))) { - - // make sure the user is actually authenticated - return userAuthentication.isAuthenticated(); - } - } - } - - // if the above doesn't pass, it's not yet approved - return false; + // TODO: make parameter name configurable? + return Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval")); } } @@ -124,12 +111,12 @@ public boolean isApproved(AuthorizationRequest authorizationRequest, Authenticat /** * Check if the user has already stored a positive approval decision for this site; or if the * site is whitelisted, approve it automatically. - * + * * Otherwise the user will be directed to the approval page and can make their own decision. - * + * * @param authorizationRequest the incoming authorization request * @param userAuthentication the Principal representing the currently-logged-in user - * + * * @return the updated AuthorizationRequest */ @Override @@ -146,7 +133,7 @@ public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizati // find out if we're supposed to force a prompt on the user or not String prompt = (String) authorizationRequest.getExtensions().get(PROMPT); List prompts = Splitter.on(PROMPT_SEPARATOR).splitToList(Strings.nullToEmpty(prompt)); - if (!prompts.contains(PROMPT_SEPARATOR)) { + if (!prompts.contains(PROMPT_CONSENT)) { // if the prompt parameter is set to "consent" then we can't use approved sites or whitelisted sites // otherwise, we need to check them below @@ -195,9 +182,7 @@ public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizati ClientDetails client = clientDetailsService.loadClientByClientId(clientId); // This must be re-parsed here because SECOAUTH forces us to call things in a strange order - if (Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval")) - && authorizationRequest.getExtensions().get(CSRF) != null - && authorizationRequest.getExtensions().get(CSRF).equals(authorizationRequest.getApprovalParameters().get(CSRF))) { + if (Boolean.parseBoolean(authorizationRequest.getApprovalParameters().get("user_oauth_approval"))) { authorizationRequest.setApproved(true); @@ -219,15 +204,7 @@ public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizati //Make sure this scope is allowed for the given client if (systemScopes.scopesMatch(client.getScope(), approveSet)) { - // If it's structured, assign the user-specified parameter - SystemScope systemScope = systemScopes.getByValue(scope); - if (systemScope != null && systemScope.isStructured()){ - String paramValue = approvalParams.get("scopeparam_" + scope); - allowedScopes.add(scope + ":"+paramValue); - // .. and if it's unstructured, we're all set - } else { - allowedScopes.add(scope); - } + allowedScopes.add(scope); } } @@ -264,7 +241,7 @@ public AuthorizationRequest updateAfterApproval(AuthorizationRequest authorizati /** * Get the auth time out of the current session and add it to the * auth request in the extensions map. - * + * * @param authorizationRequest */ private void setAuthTime(AuthorizationRequest authorizationRequest) { diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java index 3be84b56e2..789f3ce184 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/util/IdTokenHashUtils.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -31,9 +32,9 @@ /** * Utility class for generating hashes for access tokens and authorization codes * to be included in an ID Token. - * + * * @author Amanda Anganes - * + * */ public class IdTokenHashUtils { @@ -44,7 +45,7 @@ public class IdTokenHashUtils { /** * Compute the SHA hash of an authorization code - * + * * @param signingAlg * @param code * @return @@ -55,7 +56,7 @@ public static Base64URL getCodeHash(JWSAlgorithm signingAlg, String code) { /** * Compute the SHA hash of a token - * + * * @param signingAlg * @param token * @return diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java index fd8d2a6087..719bfc8d0e 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/AbstractClientEntityView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.mitre.oauth2.model.PKCEAlgorithm; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -45,14 +47,15 @@ import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; /** - * + * * Abstract superclass for client entity view, used with the ClientApi. - * + * * @see ClientEntityViewForUsers * @see ClientEntityViewForAdmins - * + * * @author jricher * */ @@ -63,52 +66,73 @@ public abstract class AbstractClientEntityView extends AbstractView { private static final Logger logger = LoggerFactory.getLogger(AbstractClientEntityView.class); private JsonParser parser = new JsonParser(); - + private Gson gson = new GsonBuilder() - .setExclusionStrategies(getExclusionStrategy()) - .registerTypeAdapter(JWSAlgorithm.class, new JsonSerializer() { - @Override - public JsonElement serialize(JWSAlgorithm src, Type typeOfSrc, JsonSerializationContext context) { - if (src != null) { - return new JsonPrimitive(src.getName()); - } else { - return null; - } - } - }) - .registerTypeAdapter(JWEAlgorithm.class, new JsonSerializer() { - @Override - public JsonElement serialize(JWEAlgorithm src, Type typeOfSrc, JsonSerializationContext context) { - if (src != null) { - return new JsonPrimitive(src.getName()); - } else { - return null; - } - } - }) - .registerTypeAdapter(EncryptionMethod.class, new JsonSerializer() { - @Override - public JsonElement serialize(EncryptionMethod src, Type typeOfSrc, JsonSerializationContext context) { - if (src != null) { - return new JsonPrimitive(src.getName()); - } else { - return null; - } - } - }) - .registerTypeAdapter(JWKSet.class, new JsonSerializer() { - @Override - public JsonElement serialize(JWKSet src, Type typeOfSrc, JsonSerializationContext context) { - if (src != null) { - return parser.parse(src.toString()); - } else { - return null; - } - } - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + .setExclusionStrategies(getExclusionStrategy()) + .registerTypeAdapter(JWSAlgorithm.class, new JsonSerializer() { + @Override + public JsonElement serialize(JWSAlgorithm src, Type typeOfSrc, JsonSerializationContext context) { + if (src != null) { + return new JsonPrimitive(src.getName()); + } else { + return null; + } + } + }) + .registerTypeAdapter(JWEAlgorithm.class, new JsonSerializer() { + @Override + public JsonElement serialize(JWEAlgorithm src, Type typeOfSrc, JsonSerializationContext context) { + if (src != null) { + return new JsonPrimitive(src.getName()); + } else { + return null; + } + } + }) + .registerTypeAdapter(EncryptionMethod.class, new JsonSerializer() { + @Override + public JsonElement serialize(EncryptionMethod src, Type typeOfSrc, JsonSerializationContext context) { + if (src != null) { + return new JsonPrimitive(src.getName()); + } else { + return null; + } + } + }) + .registerTypeAdapter(JWKSet.class, new JsonSerializer() { + @Override + public JsonElement serialize(JWKSet src, Type typeOfSrc, JsonSerializationContext context) { + if (src != null) { + return parser.parse(src.toString()); + } else { + return null; + } + } + }) + .registerTypeAdapter(JWT.class, new JsonSerializer() { + @Override + public JsonElement serialize(JWT src, Type typeOfSrc, JsonSerializationContext context) { + if (src != null) { + return new JsonPrimitive(src.serialize()); + } else { + return null; + } + } + + }) + .registerTypeAdapter(PKCEAlgorithm.class, new JsonSerializer() { + @Override + public JsonPrimitive serialize(PKCEAlgorithm src, Type typeOfSrc, JsonSerializationContext context) { + if (src != null) { + return new JsonPrimitive(src.getName()); + } else { + return null; + } + } + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); /** diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java index ad6d3d9512..7fe86f7f77 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForAdmins.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -29,9 +30,9 @@ import com.google.gson.FieldAttributes; /** - * + * * View bean for full view of client entity, for admins. - * + * * @see ClientEntityViewForUsers * @author jricher * diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java index 7e3f05f47c..11f1e51dc4 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientEntityViewForUsers.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -29,9 +30,9 @@ import com.google.gson.FieldAttributes; /** - * + * * View bean for field-limited view of client entity, for regular users. - * + * * @see AbstractClientEntityView * @see ClientEntityViewForAdmins * @author jricher diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java index 10c3c12114..a4b245179e 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/ClientInformationResponseView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -40,10 +41,10 @@ import com.google.gson.JsonObject; /** - * + * * Provides representation of a client's registration metadata, to be shown from the dynamic registration endpoint * on the client_register and rotate_secret operations. - * + * * @author jricher * */ diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java index 0e8ff0b01a..bea53cd173 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/HttpCodeView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -37,7 +38,7 @@ public class HttpCodeView extends AbstractView { public static final String VIEWNAME = "httpCodeView"; - + public static final String CODE = "code"; @Override diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java index 35b866b7b0..4b84f1a8cf 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonApprovedSiteView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -61,40 +62,40 @@ public class JsonApprovedSiteView extends AbstractView { public static final String VIEWNAME = "jsonApprovedSiteView"; private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; - } - return false; - } - - }) - .registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer() { - @Override - public JsonElement serialize(OAuth2AccessTokenEntity src, - Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.getId()); - } - }) - .registerTypeAdapter(WhitelistedSite.class, new JsonSerializer() { - @Override - public JsonElement serialize(WhitelistedSite src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(src.getId()); - } - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } + + }) + .registerTypeAdapter(OAuth2AccessTokenEntity.class, new JsonSerializer() { + @Override + public JsonElement serialize(OAuth2AccessTokenEntity src, + Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getId()); + } + }) + .registerTypeAdapter(WhitelistedSite.class, new JsonSerializer() { + @Override + public JsonElement serialize(WhitelistedSite src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getId()); + } + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java index ef5f0c0646..a9e9401c67 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonEntityView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -56,32 +57,33 @@ public class JsonEntityView extends AbstractView { public static final String VIEWNAME = "jsonEntityView"; private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; } - return false; - } - - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } + + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); HttpStatus code = (HttpStatus) model.get(HttpCodeView.CODE); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java index 520577f505..db21fffb26 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/JsonErrorView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -46,12 +47,12 @@ public class JsonErrorView extends AbstractView { /** - * + * */ public static final String ERROR_MESSAGE = "errorMessage"; /** - * + * */ public static final String ERROR = "error"; @@ -63,27 +64,27 @@ public class JsonErrorView extends AbstractView { public static final String VIEWNAME = "jsonErrorView"; private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; - } - return false; - } - - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; + } + + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java index fa822bfff7..e452b352b5 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoJWTView.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +14,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.view; @@ -71,8 +70,8 @@ public class UserInfoJWTView extends UserInfoView { public static final String JOSE_MEDIA_TYPE_VALUE = "application/jwt"; public static final MediaType JOSE_MEDIA_TYPE = new MediaType("application", "jwt"); - - + + @Autowired private JWTSigningAndValidationService jwtService; @@ -98,19 +97,16 @@ protected void writeOut(JsonObject json, Map model, response.setContentType(JOSE_MEDIA_TYPE_VALUE); - JWTClaimsSet claims = JWTClaimsSet.parse(writer.toString()); - - claims.setAudience(Lists.newArrayList(client.getClientId())); + JWTClaimsSet claims = new JWTClaimsSet.Builder(JWTClaimsSet.parse(writer.toString())) + .audience(Lists.newArrayList(client.getClientId())) + .issuer(config.getIssuer()) + .issueTime(new Date()) + .jwtID(UUID.randomUUID().toString()) // set a random NONCE in the middle of it + .build(); - claims.setIssuer(config.getIssuer()); - claims.setIssueTime(new Date()); - - claims.setJWTID(UUID.randomUUID().toString()); // set a random NONCE in the middle of it - - - if (client.getIdTokenEncryptedResponseAlg() != null && !client.getIdTokenEncryptedResponseAlg().equals(Algorithm.NONE) - && client.getIdTokenEncryptedResponseEnc() != null && !client.getIdTokenEncryptedResponseEnc().equals(Algorithm.NONE) + if (client.getUserInfoEncryptedResponseAlg() != null && !client.getUserInfoEncryptedResponseAlg().equals(Algorithm.NONE) + && client.getUserInfoEncryptedResponseEnc() != null && !client.getUserInfoEncryptedResponseEnc().equals(Algorithm.NONE) && (!Strings.isNullOrEmpty(client.getJwksUri()) || client.getJwks() != null)) { // encrypt it to the client's key @@ -119,7 +115,7 @@ protected void writeOut(JsonObject json, Map model, if (encrypter != null) { - EncryptedJWT encrypted = new EncryptedJWT(new JWEHeader(client.getIdTokenEncryptedResponseAlg(), client.getIdTokenEncryptedResponseEnc()), claims); + EncryptedJWT encrypted = new EncryptedJWT(new JWEHeader(client.getUserInfoEncryptedResponseAlg(), client.getUserInfoEncryptedResponseEnc()), claims); encrypter.encryptJwt(encrypted); @@ -136,8 +132,10 @@ protected void writeOut(JsonObject json, Map model, if (client.getUserInfoSignedResponseAlg() != null) { signingAlg = client.getUserInfoSignedResponseAlg(); // override with the client's preference if available } - - SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims); + JWSHeader header = new JWSHeader(signingAlg, null, null, null, null, null, null, null, null, null, + jwtService.getDefaultSignerKeyId(), + null, null); + SignedJWT signed = new SignedJWT(header, claims); if (signingAlg.equals(JWSAlgorithm.HS256) || signingAlg.equals(JWSAlgorithm.HS384) diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java index a3fcb81d77..73ca617f98 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/view/UserInfoView.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -53,7 +54,7 @@ public class UserInfoView extends AbstractView { public static final String USER_INFO = "userInfo"; public static final String VIEWNAME = "userInfoView"; - + private static JsonParser jsonParser = new JsonParser(); /** @@ -85,7 +86,7 @@ public boolean shouldSkipClass(Class clazz) { /* * (non-Javadoc) - * + * * @see * org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel * (java.util.Map, javax.servlet.http.HttpServletRequest, @@ -99,6 +100,7 @@ protected void renderMergedOutputModel(Map model, HttpServletReq Set scope = (Set) model.get(SCOPE); response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); JsonObject authorizedClaims = null; @@ -128,10 +130,10 @@ protected void writeOut(JsonObject json, Map model, HttpServletR /** * Build a JSON response according to the request object received. - * + * * Claims requested in requestObj.userinfo.claims are added to any * claims corresponding to requested scopes, if any. - * + * * @param ui the UserInfo to filter * @param scope the allowed scopes to filter by * @param authorizedClaims the claims authorized by the client or user @@ -144,21 +146,8 @@ private JsonObject toJsonFromRequestObj(UserInfo ui, Set scope, JsonObje JsonObject obj = ui.toJson(); Set allowedByScope = translator.getClaimsForScopeSet(scope); - Set authorizedByClaims = new HashSet<>(); - Set requestedByClaims = new HashSet<>(); - - if (authorizedClaims != null) { - JsonObject userinfoAuthorized = authorizedClaims.getAsJsonObject().get("userinfo").getAsJsonObject(); - for (Entry entry : userinfoAuthorized.getAsJsonObject().entrySet()) { - authorizedByClaims.add(entry.getKey()); - } - } - if (requestedClaims != null) { - JsonObject userinfoRequested = requestedClaims.getAsJsonObject().get("userinfo").getAsJsonObject(); - for (Entry entry : userinfoRequested.getAsJsonObject().entrySet()) { - requestedByClaims.add(entry.getKey()); - } - } + Set authorizedByClaims = extractUserInfoClaimsIntoSet(authorizedClaims); + Set requestedByClaims = extractUserInfoClaimsIntoSet(requestedClaims); // Filter claims by performing a manual intersection of claims that are allowed by the given scope, requested, and authorized. // We cannot use Sets.intersection() or similar because Entry<> objects will evaluate to being unequal if their values are @@ -179,4 +168,22 @@ private JsonObject toJsonFromRequestObj(UserInfo ui, Set scope, JsonObje return result; } + + /** + * Pull the claims that have been targeted into a set for processing. + * Returns an empty set if the input is null. + * @param claims the claims request to process + */ + private Set extractUserInfoClaimsIntoSet(JsonObject claims) { + Set target = new HashSet<>(); + if (claims != null) { + JsonObject userinfoAuthorized = claims.getAsJsonObject("userinfo"); + if (userinfoAuthorized != null) { + for (Entry entry : userinfoAuthorized.entrySet()) { + target.add(entry.getKey()); + } + } + } + return target; + } } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java index 0f71b8a8f9..1f05baab56 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ApprovedSiteAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,14 +16,13 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; import java.security.Principal; import java.util.Collection; -import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.mitre.openid.connect.model.ApprovedSite; import org.mitre.openid.connect.service.ApprovedSiteService; import org.mitre.openid.connect.view.HttpCodeView; @@ -55,9 +55,6 @@ public class ApprovedSiteAPI { @Autowired private ApprovedSiteService approvedSiteService; - @Autowired - private OAuth2TokenEntityService tokenServices; - /** * Logger for this class */ @@ -80,7 +77,7 @@ public String getAllApprovedSites(ModelMap m, Principal p) { /** * Delete an approved site - * + * */ @RequestMapping(value="/{id}", method = RequestMethod.DELETE) public String deleteApprovedSite(@PathVariable("id") Long id, ModelMap m, Principal p) { diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java index 0bd955a42d..4b5bd4c2e0 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/AuthenticationTimeStamper.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; @@ -37,7 +38,7 @@ /** * This class sets a timestamp on the current HttpSession * when someone successfully authenticates. - * + * * @author jricher * */ diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java index 39fe23c8ec..6757df8080 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/BlacklistAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; @@ -168,7 +169,7 @@ public String updateBlacklistedSite(@PathVariable("id") Long id, @RequestBody St /** * Delete a blacklisted site - * + * */ @RequestMapping(value="/{id}", method = RequestMethod.DELETE) public String deleteBlacklistedSite(@PathVariable("id") Long id, ModelMap m) { diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java index 10dd47f25c..45ba59901c 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,14 +18,24 @@ package org.mitre.openid.connect.web; import java.lang.reflect.Type; +import java.sql.SQLIntegrityConstraintViolationException; import java.text.ParseException; import java.util.Collection; +import javax.persistence.PersistenceException; + +import org.eclipse.persistence.exceptions.DatabaseException; +import org.mitre.jwt.assertion.AssertionValidator; 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.PKCEAlgorithm; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.web.AuthenticationUtilities; -import org.mitre.openid.connect.service.UserInfoService; +import org.mitre.openid.connect.exception.ValidationException; +import org.mitre.openid.connect.model.CachedImage; +import org.mitre.openid.connect.service.ClientLogoLoadingService; import org.mitre.openid.connect.view.ClientEntityViewForAdmins; import org.mitre.openid.connect.view.ClientEntityViewForUsers; import org.mitre.openid.connect.view.HttpCodeView; @@ -33,10 +44,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PathVariable; @@ -46,6 +61,7 @@ import org.springframework.web.servlet.ModelAndView; import com.google.common.base.Strings; +import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; @@ -60,6 +76,48 @@ import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +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.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.SECTOR_IDENTIFIER_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT; +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; /** * @author Michael Jett @@ -76,58 +134,86 @@ public class ClientAPI { private ClientDetailsEntityService clientService; @Autowired - private UserInfoService userInfoService; + private ClientLogoLoadingService clientLogoLoadingService; + + @Autowired + @Qualifier("clientAssertionValidator") + private AssertionValidator assertionValidator; private JsonParser parser = new JsonParser(); private Gson gson = new GsonBuilder() - .serializeNulls() - .registerTypeAdapter(JWSAlgorithm.class, new JsonDeserializer() { - @Override - public JWSAlgorithm deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonPrimitive()) { - return JWSAlgorithm.parse(json.getAsString()); - } else { - return null; - } - } - }) - .registerTypeAdapter(JWEAlgorithm.class, new JsonDeserializer() { - @Override - public JWEAlgorithm deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonPrimitive()) { - return JWEAlgorithm.parse(json.getAsString()); - } else { - return null; - } - } - }) - .registerTypeAdapter(EncryptionMethod.class, new JsonDeserializer() { - @Override - public EncryptionMethod deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonPrimitive()) { - return EncryptionMethod.parse(json.getAsString()); - } else { - return null; - } - } - }) - .registerTypeAdapter(JWKSet.class, new JsonDeserializer() { - @Override - public JWKSet deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (json.isJsonObject()) { - try { - return JWKSet.parse(json.toString()); - } catch (ParseException e) { - return null; + .serializeNulls() + .registerTypeAdapter(JWSAlgorithm.class, new JsonDeserializer() { + @Override + public JWSAlgorithm deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonPrimitive()) { + return JWSAlgorithm.parse(json.getAsString()); + } else { + return null; + } } - } else { - return null; - } - } - }) - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .create(); + }) + .registerTypeAdapter(JWEAlgorithm.class, new JsonDeserializer() { + @Override + public JWEAlgorithm deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonPrimitive()) { + return JWEAlgorithm.parse(json.getAsString()); + } else { + return null; + } + } + }) + .registerTypeAdapter(EncryptionMethod.class, new JsonDeserializer() { + @Override + public EncryptionMethod deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonPrimitive()) { + return EncryptionMethod.parse(json.getAsString()); + } else { + return null; + } + } + }) + .registerTypeAdapter(JWKSet.class, new JsonDeserializer() { + @Override + public JWKSet deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonObject()) { + try { + return JWKSet.parse(json.toString()); + } catch (ParseException e) { + return null; + } + } else { + return null; + } + } + }) + .registerTypeAdapter(JWT.class, new JsonDeserializer() { + @Override + public JWT deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonPrimitive()) { + try { + return JWTParser.parse(json.getAsString()); + } catch (ParseException e) { + return null; + } + } else { + return null; + } + } + }) + .registerTypeAdapter(PKCEAlgorithm.class, new JsonDeserializer() { + @Override + public PKCEAlgorithm deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + if (json.isJsonPrimitive()) { + return PKCEAlgorithm.parse(json.getAsString()); + } else { + return null; + } + } + }) + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); /** * Logger for this class @@ -169,8 +255,8 @@ public String apiAddClient(@RequestBody String jsonString, Model m, Authenticati try { json = parser.parse(jsonString).getAsJsonObject(); client = gson.fromJson(json, ClientDetailsEntity.class); - } - catch (JsonSyntaxException e) { + client = validateSoftwareStatement(client); + } catch (JsonSyntaxException e) { logger.error("apiAddClient failed due to JsonSyntaxException", e); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not save new client. The server encountered a JSON syntax exception. Contact a system administrator for assistance."); @@ -180,6 +266,11 @@ public String apiAddClient(@RequestBody String jsonString, Model m, Authenticati m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not save new client. The server encountered an IllegalStateException. Refresh and try again - if the problem persists, contact a system administrator for assistance."); return JsonErrorView.VIEWNAME; + } catch (ValidationException e) { + logger.error("apiUpdateClient failed due to ValidationException", e); + m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a ValidationException."); + return JsonErrorView.VIEWNAME; } // if they leave the client identifier empty, force it to be generated @@ -224,13 +315,13 @@ public String apiAddClient(@RequestBody String jsonString, Model m, Authenticati } - + client.setDynamicallyRegistered(false); try { ClientDetailsEntity newClient = clientService.saveNewClient(client); m.addAttribute(JsonEntityView.ENTITY, newClient); - + if (AuthenticationUtilities.isAdmin(auth)) { return ClientEntityViewForAdmins.VIEWNAME; } else { @@ -239,8 +330,20 @@ public String apiAddClient(@RequestBody String jsonString, Model m, Authenticati } catch (IllegalArgumentException e) { logger.error("Unable to save client: {}", e.getMessage()); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); - m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client"); + m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client: " + e.getMessage()); return JsonErrorView.VIEWNAME; + } catch (PersistenceException e) { + Throwable cause = e.getCause(); + if (cause instanceof DatabaseException) { + Throwable databaseExceptionCause = cause.getCause(); + if(databaseExceptionCause instanceof SQLIntegrityConstraintViolationException) { + logger.error("apiAddClient failed; duplicate client id entry found: {}", client.getClientId()); + m.addAttribute(HttpCodeView.CODE, HttpStatus.CONFLICT); + m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client. Duplicate client id entry found: " + client.getClientId()); + return JsonErrorView.VIEWNAME; + } + } + throw e; } } @@ -263,8 +366,8 @@ public String apiUpdateClient(@PathVariable("id") Long id, @RequestBody String j // parse the client passed in (from JSON) and fetch the old client from the store json = parser.parse(jsonString).getAsJsonObject(); client = gson.fromJson(json, ClientDetailsEntity.class); - } - catch (JsonSyntaxException e) { + client = validateSoftwareStatement(client); + } catch (JsonSyntaxException e) { logger.error("apiUpdateClient failed due to JsonSyntaxException", e); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a JSON syntax exception. Contact a system administrator for assistance."); @@ -274,6 +377,11 @@ public String apiUpdateClient(@PathVariable("id") Long id, @RequestBody String j m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered an IllegalStateException. Refresh and try again - if the problem persists, contact a system administrator for assistance."); return JsonErrorView.VIEWNAME; + } catch (ValidationException e) { + logger.error("apiUpdateClient failed due to ValidationException", e); + m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); + m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Could not update client. The server encountered a ValidationException."); + return JsonErrorView.VIEWNAME; } ClientDetailsEntity oldClient = clientService.getClientById(id); @@ -331,7 +439,7 @@ public String apiUpdateClient(@PathVariable("id") Long id, @RequestBody String j try { ClientDetailsEntity newClient = clientService.updateClient(oldClient, client); m.addAttribute(JsonEntityView.ENTITY, newClient); - + if (AuthenticationUtilities.isAdmin(auth)) { return ClientEntityViewForAdmins.VIEWNAME; } else { @@ -340,7 +448,7 @@ public String apiUpdateClient(@PathVariable("id") Long id, @RequestBody String j } catch (IllegalArgumentException e) { logger.error("Unable to save client: {}", e.getMessage()); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); - m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client"); + m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Unable to save client: " + e.getMessage()); return JsonErrorView.VIEWNAME; } } @@ -398,4 +506,167 @@ public String apiShowClient(@PathVariable("id") Long id, Model model, Authentica } } + /** + * Get the logo image for a client + * @param id + */ + @RequestMapping(value = "/{id}/logo", method=RequestMethod.GET, produces = { MediaType.IMAGE_GIF_VALUE, MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE }) + public ResponseEntity getClientLogo(@PathVariable("id") Long id, Model model) { + + ClientDetailsEntity client = clientService.getClientById(id); + + if (client == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } else if (Strings.isNullOrEmpty(client.getLogoUri())) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } else { + // get the image from cache + CachedImage image = clientLogoLoadingService.getLogo(client); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.parseMediaType(image.getContentType())); + headers.setContentLength(image.getLength()); + + return new ResponseEntity<>(image.getData(), headers, HttpStatus.OK); + } + } + + private ClientDetailsEntity validateSoftwareStatement(ClientDetailsEntity newClient) throws ValidationException { + if (newClient.getSoftwareStatement() != null) { + if (assertionValidator.isValid(newClient.getSoftwareStatement())) { + // we have a software statement and its envelope passed all the checks from our validator + + // swap out all of the client's fields for the associated parts of the software statement + try { + JWTClaimsSet claimSet = newClient.getSoftwareStatement().getJWTClaimsSet(); + for (String claim : claimSet.getClaims().keySet()) { + switch (claim) { + case SOFTWARE_STATEMENT: + throw new ValidationException("invalid_client_metadata", "Software statement can't include another software statement", HttpStatus.BAD_REQUEST); + case CLAIMS_REDIRECT_URIS: + newClient.setClaimsRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case CLIENT_SECRET_EXPIRES_AT: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client secret expiration time", HttpStatus.BAD_REQUEST); + case CLIENT_ID_ISSUED_AT: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client ID issuance time", HttpStatus.BAD_REQUEST); + case REGISTRATION_CLIENT_URI: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client configuration endpoint", HttpStatus.BAD_REQUEST); + case REGISTRATION_ACCESS_TOKEN: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client registration access token", HttpStatus.BAD_REQUEST); + case REQUEST_URIS: + newClient.setRequestUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case POST_LOGOUT_REDIRECT_URIS: + newClient.setPostLogoutRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case INITIATE_LOGIN_URI: + newClient.setInitiateLoginUri(claimSet.getStringClaim(claim)); + break; + case DEFAULT_ACR_VALUES: + newClient.setDefaultACRvalues(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case REQUIRE_AUTH_TIME: + newClient.setRequireAuthTime(claimSet.getBooleanClaim(claim)); + break; + case DEFAULT_MAX_AGE: + newClient.setDefaultMaxAge(claimSet.getIntegerClaim(claim)); + break; + case TOKEN_ENDPOINT_AUTH_SIGNING_ALG: + newClient.setTokenEndpointAuthSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case ID_TOKEN_ENCRYPTED_RESPONSE_ENC: + newClient.setIdTokenEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim))); + break; + case ID_TOKEN_ENCRYPTED_RESPONSE_ALG: + newClient.setIdTokenEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case ID_TOKEN_SIGNED_RESPONSE_ALG: + newClient.setIdTokenSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case USERINFO_ENCRYPTED_RESPONSE_ENC: + newClient.setUserInfoEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim))); + break; + case USERINFO_ENCRYPTED_RESPONSE_ALG: + newClient.setUserInfoEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case USERINFO_SIGNED_RESPONSE_ALG: + newClient.setUserInfoSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case REQUEST_OBJECT_SIGNING_ALG: + newClient.setRequestObjectSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case SUBJECT_TYPE: + newClient.setSubjectType(SubjectType.getByValue(claimSet.getStringClaim(claim))); + break; + case SECTOR_IDENTIFIER_URI: + newClient.setSectorIdentifierUri(claimSet.getStringClaim(claim)); + break; + case APPLICATION_TYPE: + newClient.setApplicationType(AppType.getByValue(claimSet.getStringClaim(claim))); + break; + case JWKS_URI: + newClient.setJwksUri(claimSet.getStringClaim(claim)); + break; + case JWKS: + newClient.setJwks(JWKSet.parse(claimSet.getJSONObjectClaim(claim).toJSONString())); + break; + case POLICY_URI: + newClient.setPolicyUri(claimSet.getStringClaim(claim)); + break; + case RESPONSE_TYPES: + newClient.setResponseTypes(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case GRANT_TYPES: + newClient.setGrantTypes(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case SCOPE: + newClient.setScope(OAuth2Utils.parseParameterList(claimSet.getStringClaim(claim))); + break; + case TOKEN_ENDPOINT_AUTH_METHOD: + newClient.setTokenEndpointAuthMethod(AuthMethod.getByValue(claimSet.getStringClaim(claim))); + break; + case TOS_URI: + newClient.setTosUri(claimSet.getStringClaim(claim)); + break; + case CONTACTS: + newClient.setContacts(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case LOGO_URI: + newClient.setLogoUri(claimSet.getStringClaim(claim)); + break; + case CLIENT_URI: + newClient.setClientUri(claimSet.getStringClaim(claim)); + break; + case CLIENT_NAME: + newClient.setClientName(claimSet.getStringClaim(claim)); + break; + case REDIRECT_URIS: + newClient.setRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case CLIENT_SECRET: + throw new ValidationException("invalid_client_metadata", "Software statement can't contain client secret", HttpStatus.BAD_REQUEST); + case CLIENT_ID: + throw new ValidationException("invalid_client_metadata", "Software statement can't contain client ID", HttpStatus.BAD_REQUEST); + + default: + logger.warn("Software statement contained unknown field: " + claim + " with value " + claimSet.getClaim(claim)); + break; + } + } + + return newClient; + } catch (ParseException e) { + throw new ValidationException("invalid_client_metadata", "Software statement claims didn't parse", HttpStatus.BAD_REQUEST); + } + } else { + throw new ValidationException("invalid_client_metadata", "Software statement rejected by validator", HttpStatus.BAD_REQUEST); + } + } else { + // nothing to see here, carry on + return newClient; + } + + } + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DataAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DataAPI.java index 992bd2de4c..84b0de503b 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DataAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DataAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -21,14 +22,13 @@ import java.security.Principal; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import javax.servlet.http.HttpServletResponse; import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.service.MITREidDataService; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_0; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_1; -import org.mitre.openid.connect.service.impl.MITREidDataService_1_2; +import org.mitre.openid.connect.service.impl.MITREidDataService_1_3; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,6 +39,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import com.google.common.collect.ImmutableList; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; @@ -46,9 +47,9 @@ /** * API endpoint for importing and exporting the current state of a server. * Includes all tokens, grants, whitelists, blacklists, and clients. - * + * * @author jricher - * + * */ @Controller @RequestMapping("/" + DataAPI.URL) @@ -68,13 +69,16 @@ public class DataAPI { private ConfigurationPropertiesBean config; @Autowired - private MITREidDataService_1_0 dataService_1_0; + private List importers; - @Autowired - private MITREidDataService_1_1 dataService_1_1; + private List supportedVersions = ImmutableList.of( + MITREidDataService.MITREID_CONNECT_1_0, + MITREidDataService.MITREID_CONNECT_1_1, + MITREidDataService.MITREID_CONNECT_1_2, + MITREidDataService.MITREID_CONNECT_1_3); @Autowired - private MITREidDataService_1_2 dataService_1_2; + private MITREidDataService_1_3 exporter; @RequestMapping(method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public String importData(Reader in, Model m) throws IOException { @@ -86,28 +90,33 @@ public String importData(Reader in, Model m) throws IOException { while (reader.hasNext()) { JsonToken tok = reader.peek(); switch (tok) { - case NAME: - String name = reader.nextName(); - if (name.equals(MITREidDataService.MITREID_CONNECT_1_0)) { - dataService_1_0.importData(reader); - } else if (name.equals(MITREidDataService.MITREID_CONNECT_1_1)) { - dataService_1_1.importData(reader); - } else if (name.equals(MITREidDataService.MITREID_CONNECT_1_2)) { - dataService_1_2.importData(reader); - } else { - // consume the next bit silently for now - logger.debug("Skipping value for " + name); // TODO: write these out? - reader.skipValue(); - } - break; - case END_OBJECT: - reader.endObject(); - break; - case END_DOCUMENT: - break; + case NAME: + String name = reader.nextName(); + + if (supportedVersions.contains(name)) { + // we're working with a known data version tag + for (MITREidDataService dataService : importers) { + // dispatch to the correct service + if (dataService.supportsVersion(name)) { + dataService.importData(reader); + break; + } + } + } else { + // consume the next bit silently for now + logger.debug("Skipping value for " + name); // TODO: write these out? + reader.skipValue(); + } + break; + case END_OBJECT: + break; + case END_DOCUMENT: + break; } } + reader.endObject(); + return "httpCodeView"; } @@ -134,7 +143,7 @@ public void exportData(HttpServletResponse resp, Principal prin) throws IOExcept writer.value(prin.getName()); // delegate to the service to do the actual export - dataService_1_2.exportData(writer); + exporter.exportData(writer); writer.endObject(); // end root writer.close(); @@ -144,4 +153,4 @@ public void exportData(HttpServletResponse resp, Principal prin) throws IOExcept } } -} \ No newline at end of file +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java index b542d30d85..79ea68116c 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/DynamicClientRegistrationEndpoint.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -23,9 +24,11 @@ import java.util.Set; import java.util.concurrent.TimeUnit; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.jwt.assertion.AssertionValidator; 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.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.oauth2.model.SystemScope; @@ -43,9 +46,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.stereotype.Controller; @@ -60,6 +65,50 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.gson.JsonSyntaxException; +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.JWTClaimsSet; + +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.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.SECTOR_IDENTIFIER_URI; +import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT; +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; @Controller @RequestMapping(value = DynamicClientRegistrationEndpoint.URL) @@ -73,9 +122,6 @@ public class DynamicClientRegistrationEndpoint { @Autowired private OAuth2TokenEntityService tokenService; - @Autowired - private JWTSigningAndValidationService jwtService; - @Autowired private SystemScopeService scopeService; @@ -88,6 +134,10 @@ public class DynamicClientRegistrationEndpoint { @Autowired private OIDCTokenService connectTokenService; + @Autowired + @Qualifier("clientAssertionValidator") + private AssertionValidator assertionValidator; + /** * Logger for this class */ @@ -127,6 +177,7 @@ public String registerNewClient(@RequestBody String jsonString, Model m) { // do validation on the fields try { + newClient = validateSoftwareStatement(newClient); // need to handle the software statement first because it might override requested values newClient = validateScopes(newClient); newClient = validateResponseTypes(newClient); newClient = validateGrantTypes(newClient); @@ -153,9 +204,26 @@ public String registerNewClient(@RequestBody String jsonString, Model m) { } // set some defaults for token timeouts - newClient.setAccessTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(1)); // access tokens good for 1hr - newClient.setIdTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(10)); // id tokens good for 10min - newClient.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked + if (config.isHeartMode()) { + // heart mode has different defaults depending on primary grant type + if (newClient.getGrantTypes().contains("authorization_code")) { + newClient.setAccessTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(1)); // access tokens good for 1hr + newClient.setIdTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(5)); // id tokens good for 5min + newClient.setRefreshTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(24)); // refresh tokens good for 24hr + } else if (newClient.getGrantTypes().contains("implicit")) { + newClient.setAccessTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(15)); // access tokens good for 15min + newClient.setIdTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(5)); // id tokens good for 5min + newClient.setRefreshTokenValiditySeconds(0); // no refresh tokens + } else if (newClient.getGrantTypes().contains("client_credentials")) { + newClient.setAccessTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(6)); // access tokens good for 6hr + newClient.setIdTokenValiditySeconds(0); // no id tokens + newClient.setRefreshTokenValiditySeconds(0); // no refresh tokens + } + } else { + newClient.setAccessTokenValiditySeconds((int)TimeUnit.HOURS.toSeconds(1)); // access tokens good for 1hr + newClient.setIdTokenValiditySeconds((int)TimeUnit.MINUTES.toSeconds(10)); // id tokens good for 10min + newClient.setRefreshTokenValiditySeconds(null); // refresh tokens good until revoked + } // this client has been dynamically registered (obviously) newClient.setDynamicallyRegistered(true); @@ -178,10 +246,6 @@ public String registerNewClient(@RequestBody String jsonString, Model m) { m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201 return ClientInformationResponseView.VIEWNAME; - } catch (UnsupportedEncodingException e) { - logger.error("Unsupported encoding", e); - m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); @@ -216,20 +280,14 @@ public String readClientConfiguration(@PathVariable("id") String clientId, Model if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { - try { - OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client); - RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8")); + OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, client); + RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8")); - // send it all out to the view - m.addAttribute("client", registered); - m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 + // send it all out to the view + m.addAttribute("client", registered); + m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 - return ClientInformationResponseView.VIEWNAME; - } catch (UnsupportedEncodingException e) { - logger.error("Unsupported encoding", e); - m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; - } + return ClientInformationResponseView.VIEWNAME; } else { // client mismatch @@ -287,6 +345,7 @@ public String updateClient(@PathVariable("id") String clientId, @RequestBody Str // do validation on the fields try { + newClient = validateSoftwareStatement(newClient); // need to handle the software statement first because it might override requested values newClient = validateScopes(newClient); newClient = validateResponseTypes(newClient); newClient = validateGrantTypes(newClient); @@ -304,7 +363,7 @@ public String updateClient(@PathVariable("id") String clientId, @RequestBody Str // save the client ClientDetailsEntity savedClient = clientService.updateClient(oldClient, newClient); - OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, savedClient); + OAuth2AccessTokenEntity token = rotateRegistrationTokenIfNecessary(auth, savedClient); RegisteredClient registered = new RegisteredClient(savedClient, token.getValue(), config.getIssuer() + "register/" + UriUtils.encodePathSegment(savedClient.getClientId(), "UTF-8")); @@ -313,10 +372,6 @@ public String updateClient(@PathVariable("id") String clientId, @RequestBody Str m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 return ClientInformationResponseView.VIEWNAME; - } catch (UnsupportedEncodingException e) { - logger.error("Unsupported encoding", e); - m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); @@ -398,6 +453,11 @@ private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) th } else { newClient.setGrantTypes(Sets.newHashSet("authorization_code")); // allow authorization code grant type by default } + if (config.isDualClient()) { + Set extendedGrandTypes = newClient.getGrantTypes(); + extendedGrandTypes.add("client_credentials"); + newClient.setGrantTypes(extendedGrandTypes); + } } // filter out unknown grant types @@ -419,7 +479,7 @@ private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) th // check for incompatible grants if (newClient.getGrantTypes().contains("implicit") || - newClient.getGrantTypes().contains("client_credentials")) { + (!config.isDualClient() && newClient.getGrantTypes().contains("client_credentials"))) { // return an error, you can't have these grant types together throw new ValidationException("invalid_client_metadata", "Incompatible grant types requested: " + newClient.getGrantTypes(), HttpStatus.BAD_REQUEST); } @@ -430,15 +490,13 @@ private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) th } newClient.getResponseTypes().add("code"); - - } if (newClient.getGrantTypes().contains("implicit")) { // check for incompatible grants if (newClient.getGrantTypes().contains("authorization_code") || - newClient.getGrantTypes().contains("client_credentials")) { + (!config.isDualClient() && newClient.getGrantTypes().contains("client_credentials"))) { // return an error, you can't have these grant types together throw new ValidationException("invalid_client_metadata", "Incompatible grant types requested: " + newClient.getGrantTypes(), HttpStatus.BAD_REQUEST); } @@ -452,14 +510,14 @@ private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) th // don't allow refresh tokens in implicit clients newClient.getGrantTypes().remove("refresh_token"); - newClient.getScope().remove("offline_access"); + newClient.getScope().remove(SystemScopeService.OFFLINE_ACCESS); } if (newClient.getGrantTypes().contains("client_credentials")) { // check for incompatible grants - if (newClient.getGrantTypes().contains("authorization_code") || - newClient.getGrantTypes().contains("implicit")) { + if (!config.isDualClient() && + (newClient.getGrantTypes().contains("authorization_code") || newClient.getGrantTypes().contains("implicit"))) { // return an error, you can't have these grant types together throw new ValidationException("invalid_client_metadata", "Incompatible grant types requested: " + newClient.getGrantTypes(), HttpStatus.BAD_REQUEST); } @@ -471,8 +529,8 @@ private ClientDetailsEntity validateGrantTypes(ClientDetailsEntity newClient) th // don't allow refresh tokens or id tokens in client_credentials clients newClient.getGrantTypes().remove("refresh_token"); - newClient.getScope().remove("offline_access"); - newClient.getScope().remove("openid"); + newClient.getScope().remove(SystemScopeService.OFFLINE_ACCESS); + newClient.getScope().remove(SystemScopeService.OPENID_SCOPE); } if (newClient.getGrantTypes().isEmpty()) { @@ -533,7 +591,155 @@ private ClientDetailsEntity validateAuth(ClientDetailsEntity newClient) throws V return newClient; } - private OAuth2AccessTokenEntity fetchValidRegistrationToken(OAuth2Authentication auth, ClientDetailsEntity client) { + + /** + * @param newClient + * @return + * @throws ValidationException + */ + private ClientDetailsEntity validateSoftwareStatement(ClientDetailsEntity newClient) throws ValidationException { + if (newClient.getSoftwareStatement() != null) { + if (assertionValidator.isValid(newClient.getSoftwareStatement())) { + // we have a software statement and its envelope passed all the checks from our validator + + // swap out all of the client's fields for the associated parts of the software statement + try { + JWTClaimsSet claimSet = newClient.getSoftwareStatement().getJWTClaimsSet(); + for (String claim : claimSet.getClaims().keySet()) { + switch (claim) { + case SOFTWARE_STATEMENT: + throw new ValidationException("invalid_client_metadata", "Software statement can't include another software statement", HttpStatus.BAD_REQUEST); + case CLAIMS_REDIRECT_URIS: + newClient.setClaimsRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case CLIENT_SECRET_EXPIRES_AT: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client secret expiration time", HttpStatus.BAD_REQUEST); + case CLIENT_ID_ISSUED_AT: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client ID issuance time", HttpStatus.BAD_REQUEST); + case REGISTRATION_CLIENT_URI: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client configuration endpoint", HttpStatus.BAD_REQUEST); + case REGISTRATION_ACCESS_TOKEN: + throw new ValidationException("invalid_client_metadata", "Software statement can't include a client registration access token", HttpStatus.BAD_REQUEST); + case REQUEST_URIS: + newClient.setRequestUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case POST_LOGOUT_REDIRECT_URIS: + newClient.setPostLogoutRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case INITIATE_LOGIN_URI: + newClient.setInitiateLoginUri(claimSet.getStringClaim(claim)); + break; + case DEFAULT_ACR_VALUES: + newClient.setDefaultACRvalues(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case REQUIRE_AUTH_TIME: + newClient.setRequireAuthTime(claimSet.getBooleanClaim(claim)); + break; + case DEFAULT_MAX_AGE: + newClient.setDefaultMaxAge(claimSet.getIntegerClaim(claim)); + break; + case TOKEN_ENDPOINT_AUTH_SIGNING_ALG: + newClient.setTokenEndpointAuthSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case ID_TOKEN_ENCRYPTED_RESPONSE_ENC: + newClient.setIdTokenEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim))); + break; + case ID_TOKEN_ENCRYPTED_RESPONSE_ALG: + newClient.setIdTokenEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case ID_TOKEN_SIGNED_RESPONSE_ALG: + newClient.setIdTokenSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case USERINFO_ENCRYPTED_RESPONSE_ENC: + newClient.setUserInfoEncryptedResponseEnc(EncryptionMethod.parse(claimSet.getStringClaim(claim))); + break; + case USERINFO_ENCRYPTED_RESPONSE_ALG: + newClient.setUserInfoEncryptedResponseAlg(JWEAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case USERINFO_SIGNED_RESPONSE_ALG: + newClient.setUserInfoSignedResponseAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case REQUEST_OBJECT_SIGNING_ALG: + newClient.setRequestObjectSigningAlg(JWSAlgorithm.parse(claimSet.getStringClaim(claim))); + break; + case SUBJECT_TYPE: + newClient.setSubjectType(SubjectType.getByValue(claimSet.getStringClaim(claim))); + break; + case SECTOR_IDENTIFIER_URI: + newClient.setSectorIdentifierUri(claimSet.getStringClaim(claim)); + break; + case APPLICATION_TYPE: + newClient.setApplicationType(AppType.getByValue(claimSet.getStringClaim(claim))); + break; + case JWKS_URI: + newClient.setJwksUri(claimSet.getStringClaim(claim)); + break; + case JWKS: + newClient.setJwks(JWKSet.parse(claimSet.getJSONObjectClaim(claim).toJSONString())); + break; + case POLICY_URI: + newClient.setPolicyUri(claimSet.getStringClaim(claim)); + break; + case RESPONSE_TYPES: + newClient.setResponseTypes(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case GRANT_TYPES: + newClient.setGrantTypes(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case SCOPE: + newClient.setScope(OAuth2Utils.parseParameterList(claimSet.getStringClaim(claim))); + break; + case TOKEN_ENDPOINT_AUTH_METHOD: + newClient.setTokenEndpointAuthMethod(AuthMethod.getByValue(claimSet.getStringClaim(claim))); + break; + case TOS_URI: + newClient.setTosUri(claimSet.getStringClaim(claim)); + break; + case CONTACTS: + newClient.setContacts(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case LOGO_URI: + newClient.setLogoUri(claimSet.getStringClaim(claim)); + break; + case CLIENT_URI: + newClient.setClientUri(claimSet.getStringClaim(claim)); + break; + case CLIENT_NAME: + newClient.setClientName(claimSet.getStringClaim(claim)); + break; + case REDIRECT_URIS: + newClient.setRedirectUris(Sets.newHashSet(claimSet.getStringListClaim(claim))); + break; + case CLIENT_SECRET: + throw new ValidationException("invalid_client_metadata", "Software statement can't contain client secret", HttpStatus.BAD_REQUEST); + case CLIENT_ID: + throw new ValidationException("invalid_client_metadata", "Software statement can't contain client ID", HttpStatus.BAD_REQUEST); + + default: + logger.warn("Software statement contained unknown field: " + claim + " with value " + claimSet.getClaim(claim)); + break; + } + } + + return newClient; + } catch (ParseException e) { + throw new ValidationException("invalid_client_metadata", "Software statement claims didn't parse", HttpStatus.BAD_REQUEST); + } + } else { + throw new ValidationException("invalid_client_metadata", "Software statement rejected by validator", HttpStatus.BAD_REQUEST); + } + } else { + // nothing to see here, carry on + return newClient; + } + + } + + + /* + * Rotates the registration token if it's expired, otherwise returns it + */ + private OAuth2AccessTokenEntity rotateRegistrationTokenIfNecessary(OAuth2Authentication auth, ClientDetailsEntity client) { OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) auth.getDetails(); OAuth2AccessTokenEntity token = tokenService.readAccessToken(details.getTokenValue()); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java new file mode 100644 index 0000000000..26055501a4 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * 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.web; + +import java.text.ParseException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.mitre.jwt.assertion.AssertionValidator; +import org.mitre.jwt.assertion.impl.SelfAssertionValidator; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.openid.connect.model.UserInfo; +import org.mitre.openid.connect.service.UserInfoService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; + +import com.google.common.base.Strings; +import com.google.common.collect.Iterables; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.JWTParser; + +/** + * Implementation of the End Session Endpoint from OIDC session management + * + * @author jricher + * + */ +@Controller +public class EndSessionEndpoint { + + public static final String URL = "endsession"; + + private static final String CLIENT_KEY = "client"; + private static final String STATE_KEY = "state"; + private static final String REDIRECT_URI_KEY = "redirectUri"; + + private static Logger logger = LoggerFactory.getLogger(EndSessionEndpoint.class); + + @Autowired + private SelfAssertionValidator validator; + + @Autowired + private UserInfoService userInfoService; + + @Autowired + private ClientDetailsEntityService clientService; + + @RequestMapping(value = "/" + URL, method = RequestMethod.GET) + public String endSession(@RequestParam (value = "id_token_hint", required = false) String idTokenHint, + @RequestParam (value = "post_logout_redirect_uri", required = false) String postLogoutRedirectUri, + @RequestParam (value = STATE_KEY, required = false) String state, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session, + Authentication auth, Model m) { + + // conditionally filled variables + JWTClaimsSet idTokenClaims = null; // pulled from the parsed and validated ID token + ClientDetailsEntity client = null; // pulled from ID token's audience field + + if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) { + session.setAttribute(REDIRECT_URI_KEY, postLogoutRedirectUri); + } + if (!Strings.isNullOrEmpty(state)) { + session.setAttribute(STATE_KEY, state); + } + + // parse the ID token hint to see if it's valid + if (!Strings.isNullOrEmpty(idTokenHint)) { + try { + JWT idToken = JWTParser.parse(idTokenHint); + + if (validator.isValid(idToken)) { + // we issued this ID token, figure out who it's for + idTokenClaims = idToken.getJWTClaimsSet(); + + String clientId = Iterables.getOnlyElement(idTokenClaims.getAudience()); + + client = clientService.loadClientByClientId(clientId); + + // save a reference in the session for us to pick up later + //session.setAttribute("endSession_idTokenHint_claims", idTokenClaims); + session.setAttribute(CLIENT_KEY, client); + } + } catch (ParseException e) { + // it's not a valid ID token, ignore it + logger.debug("Invalid id token hint", e); + } catch (InvalidClientException e) { + // couldn't find the client, ignore it + logger.debug("Invalid client", e); + } + } + + // are we logged in or not? + if (auth == null || !request.isUserInRole("ROLE_USER")) { + // we're not logged in anyway, process the final redirect bits if needed + return processLogout(null, request, response, session, auth, m); + } else { + // we are logged in, need to prompt the user before we log out + + // see who the current user is + UserInfo ui = userInfoService.getByUsername(auth.getName()); + + if (idTokenClaims != null) { + String subject = idTokenClaims.getSubject(); + // see if the current user is the same as the one in the ID token + // TODO: should we do anything different in these cases? + if (!Strings.isNullOrEmpty(subject) && subject.equals(ui.getSub())) { + // it's the same user + } else { + // it's not the same user + } + } + + m.addAttribute("client", client); + m.addAttribute("idToken", idTokenClaims); + + // display the log out confirmation page + return "logoutConfirmation"; + } + } + + @RequestMapping(value = "/" + URL, method = RequestMethod.POST) + public String processLogout(@RequestParam(value = "approve", required = false) String approved, + HttpServletRequest request, + HttpServletResponse response, + HttpSession session, + Authentication auth, Model m) { + + String redirectUri = (String) session.getAttribute(REDIRECT_URI_KEY); + String state = (String) session.getAttribute(STATE_KEY); + ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT_KEY); + + if (!Strings.isNullOrEmpty(approved)) { + // use approved, perform the logout + if (auth != null){ + new SecurityContextLogoutHandler().logout(request, response, auth); + } + SecurityContextHolder.getContext().setAuthentication(null); + // TODO: hook into other logout post-processing + } + + // if the user didn't approve, don't log out but hit the landing page anyway for redirect as needed + + + + // if we have a client AND the client has post-logout redirect URIs + // registered AND the URI given is in that list, then... + if (!Strings.isNullOrEmpty(redirectUri) && + client != null && client.getPostLogoutRedirectUris() != null) { + + if (client.getPostLogoutRedirectUris().contains(redirectUri)) { + // TODO: future, add the redirect URI to the model for the display page for an interstitial + // m.addAttribute("redirectUri", postLogoutRedirectUri); + + UriComponents uri = UriComponentsBuilder.fromHttpUrl(redirectUri).queryParam("state", state).build(); + + return "redirect:" + uri; + } + } + + // otherwise, return to a nice post-logout landing page + return "postLogout"; + } + +} diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java index c3f0ba194d..0c102a5494 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/JWKSetPublishingEndpoint.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -32,7 +33,7 @@ public class JWKSetPublishingEndpoint { public static final String URL = "jwk"; - + @Autowired private JWTSigningAndValidationService jwtService; diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java index 7c435d8514..5be40caa34 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ProtectedResourceRegistrationEndpoint.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -22,7 +21,6 @@ import java.util.HashSet; import java.util.Set; -import org.mitre.jwt.signer.service.JWTSigningAndValidationService; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; @@ -34,7 +32,6 @@ import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.exception.ValidationException; -import org.mitre.openid.connect.service.BlacklistedSiteService; import org.mitre.openid.connect.service.OIDCTokenService; import org.mitre.openid.connect.view.ClientInformationResponseView; import org.mitre.openid.connect.view.HttpCodeView; @@ -63,7 +60,7 @@ public class ProtectedResourceRegistrationEndpoint { /** - * + * */ public static final String URL = "resource"; @@ -73,15 +70,9 @@ public class ProtectedResourceRegistrationEndpoint { @Autowired private OAuth2TokenEntityService tokenService; - @Autowired - private JWTSigningAndValidationService jwtService; - @Autowired private SystemScopeService scopeService; - @Autowired - private BlacklistedSiteService blacklistService; - @Autowired private ConfigurationPropertiesBean config; @@ -186,10 +177,6 @@ public String registerNewProtectedResource(@RequestBody String jsonString, Model m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); // http 201 return ClientInformationResponseView.VIEWNAME; - } catch (UnsupportedEncodingException e) { - logger.error("Unsupported encoding", e); - m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); @@ -220,7 +207,7 @@ private ClientDetailsEntity validateScopes(ClientDetailsEntity newClient) throws if (allowedScopes == null || allowedScopes.isEmpty()) { allowedScopes = scopeService.getDefaults(); } - + newClient.setScope(scopeService.toStrings(allowedScopes)); return newClient; @@ -240,25 +227,18 @@ public String readResourceConfiguration(@PathVariable("id") String clientId, Mod ClientDetailsEntity client = clientService.loadClientByClientId(clientId); if (client != null && client.getClientId().equals(auth.getOAuth2Request().getClientId())) { + + // possibly update the token + OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client); + RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8")); + // send it all out to the view + m.addAttribute("client", registered); + m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 - try { - // possibly update the token - OAuth2AccessTokenEntity token = fetchValidRegistrationToken(auth, client); - - RegisteredClient registered = new RegisteredClient(client, token.getValue(), config.getIssuer() + "resource/" + UriUtils.encodePathSegment(client.getClientId(), "UTF-8")); - - // send it all out to the view - m.addAttribute("client", registered); - m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 - - return ClientInformationResponseView.VIEWNAME; - } catch (UnsupportedEncodingException e) { - logger.error("Unsupported encoding", e); - m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; - } + return ClientInformationResponseView.VIEWNAME; + } else { // client mismatch logger.error("readResourceConfiguration failed, client ID mismatch: " @@ -365,10 +345,6 @@ public String updateProtectedResource(@PathVariable("id") String clientId, @Requ m.addAttribute(HttpCodeView.CODE, HttpStatus.OK); // http 200 return ClientInformationResponseView.VIEWNAME; - } catch (UnsupportedEncodingException e) { - logger.error("Unsupported encoding", e); - m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); - return HttpCodeView.VIEWNAME; } catch (IllegalArgumentException e) { logger.error("Couldn't save client", e); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java index 7fbc42e780..6f08be5e06 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/RootController.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -31,7 +32,7 @@ @Controller public class RootController { - + public static final String API_URL = "api"; @Autowired diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java index 7dac1fe46d..f16693613f 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ServerConfigInterceptor.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; @@ -23,14 +24,15 @@ import javax.servlet.http.HttpServletResponse; import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.config.UIConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /** - * - * Injects the server configuration bean into the request context. + * + * Injects the server configuration bean into the request context. * This allows JSPs and the like to call "config.logoUrl" among others. - * + * * @author jricher * */ @@ -39,10 +41,14 @@ public class ServerConfigInterceptor extends HandlerInterceptorAdapter { @Autowired private ConfigurationPropertiesBean config; + @Autowired + private UIConfiguration ui; + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { request.setAttribute("config", config); + request.setAttribute("ui", ui); return true; } - + } diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/StatsAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/StatsAPI.java index a1f9b3c7cf..d090f77216 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/StatsAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/StatsAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -18,6 +19,7 @@ import java.util.Map; +import org.mitre.openid.connect.model.ClientStat; import org.mitre.openid.connect.service.StatsService; import org.mitre.openid.connect.view.JsonEntityView; import org.slf4j.Logger; @@ -53,20 +55,20 @@ public String statsSummary(ModelMap m) { } - @PreAuthorize("hasRole('ROLE_USER')") - @RequestMapping(value = "byclientid", produces = MediaType.APPLICATION_JSON_VALUE) - public String statsByClient(ModelMap m) { - Map e = statsService.getByClientId(); - - m.put(JsonEntityView.ENTITY, e); - - return JsonEntityView.VIEWNAME; - } - + // @PreAuthorize("hasRole('ROLE_USER')") + // @RequestMapping(value = "byclientid", produces = MediaType.APPLICATION_JSON_VALUE) + // public String statsByClient(ModelMap m) { + // Map e = statsService.getByClientId(); + // + // m.put(JsonEntityView.ENTITY, e); + // + // return JsonEntityView.VIEWNAME; + // } + // @PreAuthorize("hasRole('ROLE_USER')") @RequestMapping(value = "byclientid/{id}", produces = MediaType.APPLICATION_JSON_VALUE) - public String statsByClientId(@PathVariable("id") Long id, ModelMap m) { - Integer e = statsService.getCountForClientId(id); + public String statsByClientId(@PathVariable("id") String clientId, ModelMap m) { + ClientStat e = statsService.getCountForClientId(clientId); m.put(JsonEntityView.ENTITY, e); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java index c3cf642b22..16950a0926 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/UserInfoEndpoint.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -45,7 +46,7 @@ /** * OpenID Connect UserInfo endpoint, as specified in Standard sec 5 and Messages sec 2.4. - * + * * @author AANGANES * */ @@ -54,7 +55,7 @@ public class UserInfoEndpoint { public static final String URL = "userinfo"; - + @Autowired private UserInfoService userInfoService; diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java index e951b03e03..8450c88ce2 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/WhitelistAPI.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.web; @@ -168,7 +169,7 @@ public String updateWhitelistedSite(@PathVariable("id") Long id, @RequestBody St /** * Delete a whitelisted site - * + * */ @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping(value="/{id}", method = RequestMethod.DELETE) diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestDatabaseConfiguration.java b/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestDatabaseConfiguration.java new file mode 100644 index 0000000000..51ab0f43c5 --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestDatabaseConfiguration.java @@ -0,0 +1,96 @@ +package org.mitre.oauth2.repository.impl; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.EntityManagerFactory; +import javax.sql.DataSource; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.orm.jpa.JpaVendorAdapter; +import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; +import org.springframework.orm.jpa.vendor.Database; +import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; +import org.springframework.transaction.PlatformTransactionManager; + +public class TestDatabaseConfiguration { + + @Autowired + private JpaVendorAdapter jpaAdapter; + + @Autowired + private DataSource dataSource; + + @Autowired + private EntityManagerFactory entityManagerFactory; + + @Bean + public JpaOAuth2TokenRepository repository() { + return new JpaOAuth2TokenRepository(); + } + + @Bean(name = "defaultPersistenceUnit") + public FactoryBean entityManagerFactory() { + LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); + factory.setPackagesToScan("org.mitre", "org.mitre"); + factory.setPersistenceProviderClass(org.eclipse.persistence.jpa.PersistenceProvider.class); + factory.setPersistenceUnitName("test" + System.currentTimeMillis()); + factory.setDataSource(dataSource); + factory.setJpaVendorAdapter(jpaAdapter); + Map jpaProperties = new HashMap(); + jpaProperties.put("eclipselink.weaving", "false"); + jpaProperties.put("eclipselink.logging.level", "INFO"); + jpaProperties.put("eclipselink.logging.level.sql", "INFO"); + jpaProperties.put("eclipselink.cache.shared.default", "false"); + factory.setJpaPropertyMap(jpaProperties); + + return factory; + } + + @Bean + public DataSource dataSource() { + return new EmbeddedDatabaseBuilder(new DefaultResourceLoader() { + @Override + public Resource getResource(String location) { + String sql; + try { + sql = new String(Files.readAllBytes(Paths.get("..", "openid-connect-server-webapp", "src", "main", + "resources", "db", "hsql", location)), UTF_8); + } catch (IOException e) { + throw new RuntimeException("Failed to read sql-script " + location, e); + } + + return new ByteArrayResource(sql.getBytes(UTF_8)); + } + }).generateUniqueName(true).setScriptEncoding(UTF_8.name()).setType(EmbeddedDatabaseType.HSQL) + .addScripts("hsql_database_tables.sql").build(); + } + + @Bean + public JpaVendorAdapter jpaAdapter() { + EclipseLinkJpaVendorAdapter adapter = new EclipseLinkJpaVendorAdapter(); + adapter.setDatabase(Database.HSQL); + adapter.setShowSql(true); + return adapter; + } + + @Bean + public PlatformTransactionManager transactionManager() { + JpaTransactionManager platformTransactionManager = new JpaTransactionManager(); + platformTransactionManager.setEntityManagerFactory(entityManagerFactory); + return platformTransactionManager; + } +} diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java b/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java new file mode 100644 index 0000000000..4ad4b355cc --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/repository/impl/TestJpaOAuth2TokenRepository.java @@ -0,0 +1,107 @@ +package org.mitre.oauth2.repository.impl; + +import static org.junit.Assert.assertEquals; + +import java.util.Set; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mitre.oauth2.model.AuthenticationHolderEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.oauth2.model.SavedUserAuthentication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.annotation.Transactional; + +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = { TestDatabaseConfiguration.class }) +@Transactional +public class TestJpaOAuth2TokenRepository { + + @Autowired + private JpaOAuth2TokenRepository repository; + + @PersistenceContext + private EntityManager entityManager; + + @Before + public void setUp(){ + createAccessToken("user1"); + createAccessToken("user1"); + createAccessToken("user2"); + createAccessToken("user2"); + + createRefreshToken("user1"); + createRefreshToken("user1"); + createRefreshToken("user2"); + createRefreshToken("user2"); + createRefreshToken("user2"); + } + + @Test + public void testGetAccessTokensByUserName() { + Set tokens = repository.getAccessTokensByUserName("user1"); + assertEquals(2, tokens.size()); + assertEquals("user1", tokens.iterator().next().getAuthenticationHolder().getUserAuth().getName()); + } + + @Test + public void testGetRefreshTokensByUserName() { + Set tokens = repository.getRefreshTokensByUserName("user2"); + assertEquals(3, tokens.size()); + assertEquals("user2", tokens.iterator().next().getAuthenticationHolder().getUserAuth().getName()); + } + + @Test + public void testGetAllAccessTokens(){ + Set tokens = repository.getAllAccessTokens(); + assertEquals(4, tokens.size()); + } + + @Test + public void testGetAllRefreshTokens(){ + Set tokens = repository.getAllRefreshTokens(); + assertEquals(5, tokens.size()); + } + + private OAuth2AccessTokenEntity createAccessToken(String name) { + SavedUserAuthentication userAuth = new SavedUserAuthentication(); + userAuth.setName(name); + userAuth = entityManager.merge(userAuth); + + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); + authHolder.setUserAuth(userAuth); + authHolder = entityManager.merge(authHolder); + + OAuth2AccessTokenEntity accessToken = new OAuth2AccessTokenEntity(); + accessToken.setAuthenticationHolder(authHolder); + + accessToken = entityManager.merge(accessToken); + + return accessToken; + } + + private OAuth2RefreshTokenEntity createRefreshToken(String name) { + SavedUserAuthentication userAuth = new SavedUserAuthentication(); + userAuth.setName(name); + userAuth = entityManager.merge(userAuth); + + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); + authHolder.setUserAuth(userAuth); + authHolder = entityManager.merge(authHolder); + + OAuth2RefreshTokenEntity refreshToken = new OAuth2RefreshTokenEntity(); + refreshToken.setAuthenticationHolder(authHolder); + + refreshToken = entityManager.merge(refreshToken); + + return refreshToken; + } + +} diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java index 8f2954e23d..3698ec9e05 100644 --- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestBlacklistAwareRedirectResolver.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,9 +16,12 @@ package org.mitre.oauth2.service.impl; +import static org.mockito.Matchers.anyString; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.service.BlacklistedSiteService; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -32,8 +34,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.mockito.Matchers.anyString; - import static org.mockito.Mockito.when; import static org.junit.Assert.assertThat; @@ -47,86 +47,111 @@ public class TestBlacklistAwareRedirectResolver { @Mock private BlacklistedSiteService blacklistService; - + @Mock private ClientDetails client; - + + @Mock + private ConfigurationPropertiesBean config; + @InjectMocks private BlacklistAwareRedirectResolver resolver; - + private String blacklistedUri = "https://evil.example.com/"; private String goodUri = "https://good.example.com/"; - + private String pathUri = "https://good.example.com/with/path"; - + /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { - + when(blacklistService.isBlacklisted(anyString())).thenReturn(false); when(blacklistService.isBlacklisted(blacklistedUri)).thenReturn(true); - + when(client.getAuthorizedGrantTypes()).thenReturn(ImmutableSet.of("authorization_code")); when(client.getRegisteredRedirectUri()).thenReturn(ImmutableSet.of(goodUri, blacklistedUri)); - + + when(config.isHeartMode()).thenReturn(false); } @Test public void testResolveRedirect_safe() { - // default uses prefix matching, both of these should work - + // default uses prefix matching, the first one should work fine + String res1 = resolver.resolveRedirect(goodUri, client); - + assertThat(res1, is(equalTo(goodUri))); - String res2 = resolver.resolveRedirect(pathUri, client); + // set the resolver to non-strict and test the path-based redirect resolution + resolver.setStrictMatch(false); + + String res2 = resolver.resolveRedirect(pathUri, client); + assertThat(res2, is(equalTo(pathUri))); - - + + } - + @Test(expected = InvalidRequestException.class) public void testResolveRedirect_blacklisted() { - - // this should fail with an error + + // this should fail with an error resolver.resolveRedirect(blacklistedUri, client); - + } @Test - public void testRedirectMatches_strict() { - resolver.setStrictMatch(true); - + public void testRedirectMatches_default() { + // this is not an exact match boolean res1 = resolver.redirectMatches(pathUri, goodUri); - + assertThat(res1, is(false)); - + // this is an exact match boolean res2 = resolver.redirectMatches(goodUri, goodUri); - + assertThat(res2, is(true)); - + } - + @Test - public void testRedirectMatches_default() { + public void testRedirectMatches_nonstrict() { + + // set the resolver to non-strict match mode + resolver.setStrictMatch(false); // this is not an exact match (but that's OK) boolean res1 = resolver.redirectMatches(pathUri, goodUri); - + assertThat(res1, is(true)); - + // this is an exact match boolean res2 = resolver.redirectMatches(goodUri, goodUri); - + assertThat(res2, is(true)); } + @Test + public void testHeartMode() { + when(config.isHeartMode()).thenReturn(true); + + // this is not an exact match + boolean res1 = resolver.redirectMatches(pathUri, goodUri); + + assertThat(res1, is(false)); + + // this is an exact match + boolean res2 = resolver.redirectMatches(goodUri, goodUri); + + assertThat(res2, is(true)); + } + } diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java index 0fc6e86249..423cab6da0 100644 --- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultIntrospectionResultAssembler.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +15,9 @@ *******************************************************************************/ package org.mitre.oauth2.service.impl; +import static com.google.common.collect.Sets.newHashSet; +import static org.mockito.BDDMockito.given; + import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -30,19 +32,17 @@ import org.mitre.oauth2.service.IntrospectionResultAssembler; import org.mitre.openid.connect.model.UserInfo; import org.mitre.uma.model.Permission; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import static com.google.common.collect.Sets.newHashSet; - import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.mockito.BDDMockito.given; - import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; @@ -59,10 +59,10 @@ public void shouldAssembleExpectedResultForAccessToken() throws ParseException { // given OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), null, "Bearer", - authentication("name", request("clientId"))); + oauth2AuthenticationWithUser(oauth2Request("clientId"), "name")); UserInfo userInfo = userInfo("sub"); - + Set authScopes = scopes("foo", "bar", "baz"); // when @@ -87,12 +87,12 @@ public void shouldAssembleExpectedResultForAccessToken() throws ParseException { public void shouldAssembleExpectedResultForAccessToken_withPermissions() throws ParseException { // given - OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), + OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), permissions(permission(1L, "foo", "bar")), - "Bearer", authentication("name", request("clientId"))); + "Bearer", oauth2AuthenticationWithUser(oauth2Request("clientId"), "name")); UserInfo userInfo = userInfo("sub"); - + Set authScopes = scopes("foo", "bar", "baz"); // when @@ -127,7 +127,7 @@ public void shouldAssembleExpectedResultForAccessTokenWithoutUserInfo() throws P // given OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), null, "Bearer", - authentication("name", request("clientId"))); + oauth2AuthenticationWithUser(oauth2Request("clientId"), "name")); Set authScopes = scopes("foo", "bar", "baz"); @@ -154,7 +154,7 @@ public void shouldAssembleExpectedResultForAccessTokenWithoutExpiry() { // given OAuth2AccessTokenEntity accessToken = accessToken(null, scopes("foo", "bar"), null, "Bearer", - authentication("name", request("clientId"))); + oauth2AuthenticationWithUser(oauth2Request("clientId"), "name")); UserInfo userInfo = userInfo("sub"); @@ -176,12 +176,37 @@ public void shouldAssembleExpectedResultForAccessTokenWithoutExpiry() { assertThat(result, is(equalTo(expected))); } + @Test + public void shouldAssembleExpectedResultForAccessTokenWithoutUserAuthentication() throws ParseException { + // given + OAuth2AccessTokenEntity accessToken = accessToken(new Date(123 * 1000L), scopes("foo", "bar"), null, "Bearer", + oauth2Authentication(oauth2Request("clientId"), null)); + + Set authScopes = scopes("foo", "bar", "baz"); + + // when + Map result = assembler.assembleFrom(accessToken, null, authScopes); + + + // then `user_id` should not be present + Map expected = new ImmutableMap.Builder() + .put("sub", "clientId") + .put("exp", 123L) + .put("expires_at", dateFormat.valueToString(new Date(123 * 1000L))) + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("client_id", "clientId") + .put("token_type", "Bearer") + .build(); + assertThat(result, is(equalTo(expected))); + } + @Test public void shouldAssembleExpectedResultForRefreshToken() throws ParseException { // given OAuth2RefreshTokenEntity refreshToken = refreshToken(new Date(123 * 1000L), - authentication("name", request("clientId", scopes("foo", "bar")))); + oauth2AuthenticationWithUser(oauth2Request("clientId", scopes("foo", "bar")), "name")); UserInfo userInfo = userInfo("sub"); @@ -209,7 +234,7 @@ public void shouldAssembleExpectedResultForRefreshTokenWithoutUserInfo() throws // given OAuth2RefreshTokenEntity refreshToken = refreshToken(new Date(123 * 1000L), - authentication("name", request("clientId", scopes("foo", "bar")))); + oauth2AuthenticationWithUser(oauth2Request("clientId", scopes("foo", "bar")), "name")); Set authScopes = scopes("foo", "bar", "baz"); @@ -235,7 +260,7 @@ public void shouldAssembleExpectedResultForRefreshTokenWithoutExpiry() { // given OAuth2RefreshTokenEntity refreshToken = refreshToken(null, - authentication("name", request("clientId", scopes("foo", "bar")))); + oauth2AuthenticationWithUser(oauth2Request("clientId", scopes("foo", "bar")), "name")); UserInfo userInfo = userInfo("sub"); @@ -256,6 +281,30 @@ public void shouldAssembleExpectedResultForRefreshTokenWithoutExpiry() { assertThat(result, is(equalTo(expected))); } + @Test + public void shouldAssembleExpectedResultForRefreshTokenWithoutUserAuthentication() throws ParseException { + // given + OAuth2RefreshTokenEntity refreshToken = refreshToken(null, + oauth2Authentication(oauth2Request("clientId", scopes("foo", "bar")), null)); + + Set authScopes = scopes("foo", "bar", "baz"); + + // when + Map result = assembler.assembleFrom(refreshToken, null, authScopes); + + + // then `user_id` should not be present + Map expected = new ImmutableMap.Builder() + .put("sub", "clientId") + .put("scope", "bar foo") + .put("active", Boolean.TRUE) + .put("client_id", "clientId") + .build(); + assertThat(result, is(equalTo(expected))); + } + + + private UserInfo userInfo(String sub) { UserInfo userInfo = mock(UserInfo.class); given(userInfo.getSub()).willReturn(sub); @@ -279,29 +328,31 @@ private OAuth2RefreshTokenEntity refreshToken(Date exp, OAuth2Authentication aut return refreshToken; } - private OAuth2Authentication authentication(String name, OAuth2Request request) { - OAuth2Authentication authentication = mock(OAuth2Authentication.class); - given(authentication.getName()).willReturn(name); - given(authentication.getOAuth2Request()).willReturn(request); - return authentication; + private OAuth2Authentication oauth2AuthenticationWithUser(OAuth2Request request, String username) { + UsernamePasswordAuthenticationToken userAuthentication = new UsernamePasswordAuthenticationToken(username, "somepassword"); + return oauth2Authentication(request, userAuthentication); + } + + private OAuth2Authentication oauth2Authentication(OAuth2Request request, Authentication userAuthentication) { + return new OAuth2Authentication(request, userAuthentication); } - private OAuth2Request request(String clientId) { - return request(clientId, null); + private OAuth2Request oauth2Request(String clientId) { + return oauth2Request(clientId, null); } - private OAuth2Request request(String clientId, Set scopes) { + private OAuth2Request oauth2Request(String clientId, Set scopes) { return new OAuth2Request(null, clientId, null, true, scopes, null, null, null, null); } private Set scopes(String... scopes) { return newHashSet(scopes); } - + private Set permissions(Permission... permissions) { return newHashSet(permissions); } - + private Permission permission(Long resourceSetId, String... scopes) { Permission permission = mock(Permission.class, RETURNS_DEEP_STUBS); given(permission.getResourceSet().getId()).willReturn(resourceSetId); diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java index a6a8939cec..6ec31ca806 100644 --- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ClientDetailsEntityService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,16 +18,19 @@ package org.mitre.oauth2.service.impl; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.SystemScope; import org.mitre.oauth2.repository.OAuth2ClientRepository; import org.mitre.oauth2.repository.OAuth2TokenRepository; import org.mitre.oauth2.service.SystemScopeService; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; import org.mitre.openid.connect.model.WhitelistedSite; import org.mitre.openid.connect.service.ApprovedSiteService; import org.mitre.openid.connect.service.BlacklistedSiteService; @@ -49,6 +53,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; @@ -77,13 +82,16 @@ public class TestDefaultOAuth2ClientDetailsEntityService { @Mock private SystemScopeService scopeService; - + @Mock private ResourceSetService resourceSetService; @Mock private StatsService statsService; + @Mock + private ConfigurationPropertiesBean config; + @InjectMocks private DefaultOAuth2ClientDetailsEntityService service; @@ -119,7 +127,7 @@ public Set answer(InvocationOnMock invocation) throws Throwable { return output; } }); - + Mockito.when(scopeService.toStrings(Matchers.anySet())).thenAnswer(new Answer>() { @Override public Set answer(InvocationOnMock invocation) throws Throwable { @@ -132,10 +140,12 @@ public Set answer(InvocationOnMock invocation) throws Throwable { return output; } }); - + // we're not testing reserved scopes here, just pass through when it's called Mockito.when(scopeService.removeReservedScopes(Matchers.anySet())).then(AdditionalAnswers.returnsFirstArg()); + Mockito.when(config.isHeartMode()).thenReturn(false); + } /** @@ -208,7 +218,7 @@ public void saveNewClient_noOfflineAccess() { client = service.saveNewClient(client); Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(false))); } @@ -270,7 +280,7 @@ public void deleteClient() { Mockito.when(whitelistedSiteService.getByClientId(clientId)).thenReturn(site); Mockito.when(resourceSetService.getAllForClient(client)).thenReturn(new HashSet()); - + service.deleteClient(client); Mockito.verify(tokenRepository).clearTokensForClient(client); @@ -333,7 +343,7 @@ public void updateClient_yesOfflineAccess() { client = service.updateClient(oldClient, client); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); + Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(true))); } @@ -350,7 +360,273 @@ public void updateClient_noOfflineAccess() { client = service.updateClient(oldClient, client); Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + assertThat(client.getScope().contains(SystemScopeService.OFFLINE_ACCESS), is(equalTo(false))); } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_authcode_invalidGrants() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + grantTypes.add("implicit"); + grantTypes.add("client_credentials"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_implicit_invalidGrants() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("implicit"); + grantTypes.add("authorization_code"); + grantTypes.add("client_credentials"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.NONE); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_clientcreds_invalidGrants() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("client_credentials"); + grantTypes.add("authorization_code"); + grantTypes.add("implicit"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_authcode_authMethod() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.SECRET_POST); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_implicit_authMethod() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("implicit"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_clientcreds_authMethod() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("client_credentials"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.SECRET_BASIC); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_authcode_redirectUris() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_implicit_redirectUris() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("implicit"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.NONE); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_clientcreds_redirectUris() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("client_credentials"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("http://foo.bar/")); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_clientSecret() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("http://foo.bar/")); + + client.setClientSecret("secret!"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_noJwks() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwks(null); + client.setJwksUri(null); + + service.saveNewClient(client); + + } + + @Test + public void heartMode_validAuthcodeClient() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + grantTypes.add("refresh_token"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("https://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + assertThat(client.getClientId(), is(notNullValue(String.class))); + assertThat(client.getClientSecret(), is(nullValue())); + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_nonLocalHttpRedirect() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + grantTypes.add("refresh_token"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("http://foo.bar/")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } + + @Test(expected = IllegalArgumentException.class) + public void heartMode_multipleRedirectClass() { + Mockito.when(config.isHeartMode()).thenReturn(true); + + ClientDetailsEntity client = new ClientDetailsEntity(); + Set grantTypes = new LinkedHashSet<>(); + grantTypes.add("authorization_code"); + grantTypes.add("refresh_token"); + client.setGrantTypes(grantTypes); + + client.setTokenEndpointAuthMethod(AuthMethod.PRIVATE_KEY); + + client.setRedirectUris(Sets.newHashSet("http://localhost/", "https://foo.bar", "foo://bar")); + + client.setJwksUri("https://foo.bar/jwks"); + + service.saveNewClient(client); + + } } diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java index 9e16b44318..afdb9dd6b0 100644 --- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultOAuth2ProviderTokenService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -32,7 +33,6 @@ import org.mitre.oauth2.repository.OAuth2TokenRepository; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.SystemScopeService; -import org.mockito.AdditionalAnswers; import org.mockito.InjectMocks; import org.mockito.Matchers; import org.mockito.Mock; @@ -49,17 +49,23 @@ import org.springframework.security.oauth2.provider.TokenRequest; import org.springframework.security.oauth2.provider.token.TokenEnhancer; -import com.google.common.collect.Sets; - +import static com.google.common.collect.Sets.newHashSet; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; - +import static org.mockito.AdditionalAnswers.returnsFirstArg; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anySet; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -80,9 +86,11 @@ public class TestDefaultOAuth2ProviderTokenService { private ClientDetailsEntity badClient; private String clientId = "test_client"; private String badClientId = "bad_client"; - private Set scope = Sets.newHashSet("openid", "profile", "email", "offline_access"); + private Set scope = newHashSet("openid", "profile", "email", "offline_access"); private OAuth2RefreshTokenEntity refreshToken; + private OAuth2AccessTokenEntity accessToken; private String refreshTokenValue = "refresh_token_value"; + private String userName = "6a50ac11786d402a9591d3e592ac770f"; private TokenRequest tokenRequest; // for use when refreshing access tokens @@ -114,48 +122,48 @@ public class TestDefaultOAuth2ProviderTokenService { */ @Before public void prepare() { - Mockito.reset(tokenRepository, authenticationHolderRepository, clientDetailsService, tokenEnhancer); - - + reset(tokenRepository, authenticationHolderRepository, clientDetailsService, tokenEnhancer); authentication = Mockito.mock(OAuth2Authentication.class); OAuth2Request clientAuth = new OAuth2Request(null, clientId, null, true, scope, null, null, null, null); - Mockito.when(authentication.getOAuth2Request()).thenReturn(clientAuth); + when(authentication.getOAuth2Request()).thenReturn(clientAuth); client = Mockito.mock(ClientDetailsEntity.class); - Mockito.when(client.getClientId()).thenReturn(clientId); - Mockito.when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(client); - Mockito.when(client.isReuseRefreshToken()).thenReturn(true); + when(client.getClientId()).thenReturn(clientId); + when(clientDetailsService.loadClientByClientId(clientId)).thenReturn(client); + when(client.isReuseRefreshToken()).thenReturn(true); // by default in tests, allow refresh tokens - Mockito.when(client.isAllowRefresh()).thenReturn(true); - + when(client.isAllowRefresh()).thenReturn(true); + // by default, clear access tokens on refresh - Mockito.when(client.isClearAccessTokensOnRefresh()).thenReturn(true); + when(client.isClearAccessTokensOnRefresh()).thenReturn(true); badClient = Mockito.mock(ClientDetailsEntity.class); - Mockito.when(badClient.getClientId()).thenReturn(badClientId); - Mockito.when(clientDetailsService.loadClientByClientId(badClientId)).thenReturn(badClient); + when(badClient.getClientId()).thenReturn(badClientId); + when(clientDetailsService.loadClientByClientId(badClientId)).thenReturn(badClient); refreshToken = Mockito.mock(OAuth2RefreshTokenEntity.class); - Mockito.when(tokenRepository.getRefreshTokenByValue(refreshTokenValue)).thenReturn(refreshToken); - Mockito.when(refreshToken.getClient()).thenReturn(client); - Mockito.when(refreshToken.isExpired()).thenReturn(false); + when(tokenRepository.getRefreshTokenByValue(refreshTokenValue)).thenReturn(refreshToken); + when(refreshToken.getClient()).thenReturn(client); + when(refreshToken.isExpired()).thenReturn(false); + + accessToken = Mockito.mock(OAuth2AccessTokenEntity.class); tokenRequest = new TokenRequest(null, clientId, null, null); storedAuthentication = authentication; storedAuthRequest = clientAuth; - storedAuthHolder = Mockito.mock(AuthenticationHolderEntity.class); - storedScope = Sets.newHashSet(scope); + storedAuthHolder = mock(AuthenticationHolderEntity.class); + storedScope = newHashSet(scope); - Mockito.when(refreshToken.getAuthenticationHolder()).thenReturn(storedAuthHolder); - Mockito.when(storedAuthHolder.getAuthentication()).thenReturn(storedAuthentication); - Mockito.when(storedAuthentication.getOAuth2Request()).thenReturn(storedAuthRequest); + when(refreshToken.getAuthenticationHolder()).thenReturn(storedAuthHolder); + when(storedAuthHolder.getAuthentication()).thenReturn(storedAuthentication); + when(storedAuthentication.getOAuth2Request()).thenReturn(storedAuthRequest); - Mockito.when(authenticationHolderRepository.save(Matchers.any(AuthenticationHolderEntity.class))).thenReturn(storedAuthHolder); + when(authenticationHolderRepository.save(any(AuthenticationHolderEntity.class))).thenReturn(storedAuthHolder); - Mockito.when(scopeService.fromStrings(Matchers.anySet())).thenAnswer(new Answer>() { + when(scopeService.fromStrings(anySet())).thenAnswer(new Answer>() { @Override public Set answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); @@ -167,8 +175,8 @@ public Set answer(InvocationOnMock invocation) throws Throwable { return output; } }); - - Mockito.when(scopeService.toStrings(Matchers.anySet())).thenAnswer(new Answer>() { + + when(scopeService.toStrings(anySet())).thenAnswer(new Answer>() { @Override public Set answer(InvocationOnMock invocation) throws Throwable { Object[] args = invocation.getArguments(); @@ -182,10 +190,10 @@ public Set answer(InvocationOnMock invocation) throws Throwable { }); // we're not testing restricted or reserved scopes here, just pass through - Mockito.when(scopeService.removeReservedScopes(Matchers.anySet())).then(AdditionalAnswers.returnsFirstArg()); - Mockito.when(scopeService.removeRestrictedAndReservedScopes(Matchers.anySet())).then(AdditionalAnswers.returnsFirstArg()); + when(scopeService.removeReservedScopes(anySet())).then(returnsFirstArg()); + when(scopeService.removeRestrictedAndReservedScopes(anySet())).then(returnsFirstArg()); - Mockito.when(tokenEnhancer.enhance(Matchers.any(OAuth2AccessTokenEntity.class), Matchers.any(OAuth2Authentication.class))) + when(tokenEnhancer.enhance(any(OAuth2AccessTokenEntity.class), any(OAuth2Authentication.class))) .thenAnswer(new Answer(){ @Override public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwable { @@ -194,7 +202,7 @@ public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwa } }); - Mockito.when(tokenRepository.saveAccessToken(Matchers.any(OAuth2AccessTokenEntity.class))) + when(tokenRepository.saveAccessToken(any(OAuth2AccessTokenEntity.class))) .thenAnswer(new Answer() { @Override public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwable { @@ -204,7 +212,7 @@ public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwa }); - Mockito.when(tokenRepository.saveRefreshToken(Matchers.any(OAuth2RefreshTokenEntity.class))) + when(tokenRepository.saveRefreshToken(any(OAuth2RefreshTokenEntity.class))) .thenAnswer(new Answer() { @Override public OAuth2RefreshTokenEntity answer(InvocationOnMock invocation) throws Throwable { @@ -220,8 +228,7 @@ public OAuth2RefreshTokenEntity answer(InvocationOnMock invocation) throws Throw */ @Test public void createAccessToken_nullAuth() { - - Mockito.when(authentication.getOAuth2Request()).thenReturn(null); + when(authentication.getOAuth2Request()).thenReturn(null); try { service.createAccessToken(null); @@ -243,8 +250,7 @@ public void createAccessToken_nullAuth() { */ @Test(expected = InvalidClientException.class) public void createAccessToken_nullClient() { - - Mockito.when(clientDetailsService.loadClientByClientId(Matchers.anyString())).thenReturn(null); + when(clientDetailsService.loadClientByClientId(anyString())).thenReturn(null); service.createAccessToken(authentication); } @@ -254,19 +260,18 @@ public void createAccessToken_nullClient() { */ @Test public void createAccessToken_noRefresh() { - - Mockito.when(client.isAllowRefresh()).thenReturn(false); + when(client.isAllowRefresh()).thenReturn(false); OAuth2AccessTokenEntity token = service.createAccessToken(authentication); - Mockito.verify(clientDetailsService).loadClientByClientId(Matchers.anyString()); - Mockito.verify(authenticationHolderRepository).save(Matchers.any(AuthenticationHolderEntity.class)); - Mockito.verify(tokenEnhancer).enhance(Matchers.any(OAuth2AccessTokenEntity.class), Matchers.eq(authentication)); - Mockito.verify(tokenRepository).saveAccessToken(Matchers.any(OAuth2AccessTokenEntity.class)); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); + verify(clientDetailsService).loadClientByClientId(anyString()); + verify(authenticationHolderRepository).save(any(AuthenticationHolderEntity.class)); + verify(tokenEnhancer).enhance(any(OAuth2AccessTokenEntity.class), Matchers.eq(authentication)); + verify(tokenRepository).saveAccessToken(any(OAuth2AccessTokenEntity.class)); + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + + verify(tokenRepository, Mockito.never()).saveRefreshToken(any(OAuth2RefreshTokenEntity.class)); - Mockito.verify(tokenRepository, Mockito.never()).saveRefreshToken(Matchers.any(OAuth2RefreshTokenEntity.class)); - assertThat(token.getRefreshToken(), is(nullValue())); } @@ -275,19 +280,17 @@ public void createAccessToken_noRefresh() { */ @Test public void createAccessToken_yesRefresh() { - - OAuth2Request clientAuth = new OAuth2Request(null, clientId, null, true, Sets.newHashSet(SystemScopeService.OFFLINE_ACCESS), null, null, null, null); - Mockito.when(authentication.getOAuth2Request()).thenReturn(clientAuth); - Mockito.when(client.isAllowRefresh()).thenReturn(true); + OAuth2Request clientAuth = new OAuth2Request(null, clientId, null, true, newHashSet(SystemScopeService.OFFLINE_ACCESS), null, null, null, null); + when(authentication.getOAuth2Request()).thenReturn(clientAuth); + when(client.isAllowRefresh()).thenReturn(true); OAuth2AccessTokenEntity token = service.createAccessToken(authentication); // Note: a refactor may be appropriate to only save refresh tokens once to the repository during creation. - Mockito.verify(tokenRepository, Mockito.atLeastOnce()).saveRefreshToken(Matchers.any(OAuth2RefreshTokenEntity.class)); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - - assertThat(token.getRefreshToken(), is(notNullValue())); + verify(tokenRepository, atLeastOnce()).saveRefreshToken(any(OAuth2RefreshTokenEntity.class)); + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getRefreshToken(), is(notNullValue())); } /** @@ -295,12 +298,11 @@ public void createAccessToken_yesRefresh() { */ @Test public void createAccessToken_expiration() { - Integer accessTokenValiditySeconds = 3600; Integer refreshTokenValiditySeconds = 600; - Mockito.when(client.getAccessTokenValiditySeconds()).thenReturn(accessTokenValiditySeconds); - Mockito.when(client.getRefreshTokenValiditySeconds()).thenReturn(refreshTokenValiditySeconds); + when(client.getAccessTokenValiditySeconds()).thenReturn(accessTokenValiditySeconds); + when(client.getRefreshTokenValiditySeconds()).thenReturn(refreshTokenValiditySeconds); long start = System.currentTimeMillis(); OAuth2AccessTokenEntity token = service.createAccessToken(authentication); @@ -312,67 +314,60 @@ public void createAccessToken_expiration() { Date lowerBoundRefreshTokens = new Date(start + (refreshTokenValiditySeconds * 1000L) - DELTA); Date upperBoundRefreshTokens = new Date(end + (refreshTokenValiditySeconds * 1000L) + DELTA); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertTrue(token.getExpiration().after(lowerBoundAccessTokens) && token.getExpiration().before(upperBoundAccessTokens)); assertTrue(token.getRefreshToken().getExpiration().after(lowerBoundRefreshTokens) && token.getRefreshToken().getExpiration().before(upperBoundRefreshTokens)); } @Test public void createAccessToken_checkClient() { - OAuth2AccessTokenEntity token = service.createAccessToken(authentication); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getClient().getClientId(), equalTo(clientId)); } @Test public void createAccessToken_checkScopes() { - OAuth2AccessTokenEntity token = service.createAccessToken(authentication); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getScope(), equalTo(scope)); } @Test public void createAccessToken_checkAttachedAuthentication() { + AuthenticationHolderEntity authHolder = mock(AuthenticationHolderEntity.class); + when(authHolder.getAuthentication()).thenReturn(authentication); - AuthenticationHolderEntity authHolder = Mockito.mock(AuthenticationHolderEntity.class); - Mockito.when(authHolder.getAuthentication()).thenReturn(authentication); - - Mockito.when(authenticationHolderRepository.save(Matchers.any(AuthenticationHolderEntity.class))).thenReturn(authHolder); + when(authenticationHolderRepository.save(any(AuthenticationHolderEntity.class))).thenReturn(authHolder); OAuth2AccessTokenEntity token = service.createAccessToken(authentication); assertThat(token.getAuthenticationHolder().getAuthentication(), equalTo(authentication)); - Mockito.verify(authenticationHolderRepository).save(Matchers.any(AuthenticationHolderEntity.class)); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(authenticationHolderRepository).save(any(AuthenticationHolderEntity.class)); + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); } @Test(expected = InvalidTokenException.class) public void refreshAccessToken_noRefreshToken() { - - Mockito.when(tokenRepository.getRefreshTokenByValue(Matchers.anyString())).thenReturn(null); + when(tokenRepository.getRefreshTokenByValue(anyString())).thenReturn(null); service.refreshAccessToken(refreshTokenValue, tokenRequest); } @Test(expected = InvalidClientException.class) public void refreshAccessToken_notAllowRefresh() { - - Mockito.when(client.isAllowRefresh()).thenReturn(false); + when(client.isAllowRefresh()).thenReturn(false); service.refreshAccessToken(refreshTokenValue, tokenRequest); } @Test(expected = InvalidClientException.class) public void refreshAccessToken_clientMismatch() { - tokenRequest = new TokenRequest(null, badClientId, null, null); service.refreshAccessToken(refreshTokenValue, tokenRequest); @@ -380,96 +375,89 @@ public void refreshAccessToken_clientMismatch() { @Test(expected = InvalidTokenException.class) public void refreshAccessToken_expired() { - - Mockito.when(refreshToken.isExpired()).thenReturn(true); + when(refreshToken.isExpired()).thenReturn(true); service.refreshAccessToken(refreshTokenValue, tokenRequest); } @Test public void refreshAccessToken_verifyAcessToken() { - OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(tokenRepository).clearAccessTokensForRefreshToken(refreshToken); + verify(tokenRepository).clearAccessTokensForRefreshToken(refreshToken); assertThat(token.getClient(), equalTo(client)); assertThat(token.getRefreshToken(), equalTo(refreshToken)); assertThat(token.getAuthenticationHolder(), equalTo(storedAuthHolder)); - Mockito.verify(tokenEnhancer).enhance(token, storedAuthentication); - Mockito.verify(tokenRepository).saveAccessToken(token); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(tokenEnhancer).enhance(token, storedAuthentication); + verify(tokenRepository).saveAccessToken(token); + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + } @Test public void refreshAccessToken_rotateRefreshToken() { - when(client.isReuseRefreshToken()).thenReturn(false); - + OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(tokenRepository).clearAccessTokensForRefreshToken(refreshToken); + verify(tokenRepository).clearAccessTokensForRefreshToken(refreshToken); assertThat(token.getClient(), equalTo(client)); assertThat(token.getRefreshToken(), not(equalTo(refreshToken))); assertThat(token.getAuthenticationHolder(), equalTo(storedAuthHolder)); - Mockito.verify(tokenEnhancer).enhance(token, storedAuthentication); - Mockito.verify(tokenRepository).saveAccessToken(token); - Mockito.verify(tokenRepository).removeRefreshToken(refreshToken); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(tokenEnhancer).enhance(token, storedAuthentication); + verify(tokenRepository).saveAccessToken(token); + verify(tokenRepository).removeRefreshToken(refreshToken); + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + } @Test public void refreshAccessToken_keepAccessTokens() { - when(client.isClearAccessTokensOnRefresh()).thenReturn(false); - + OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(tokenRepository, never()).clearAccessTokensForRefreshToken(refreshToken); + verify(tokenRepository, never()).clearAccessTokensForRefreshToken(refreshToken); assertThat(token.getClient(), equalTo(client)); assertThat(token.getRefreshToken(), equalTo(refreshToken)); assertThat(token.getAuthenticationHolder(), equalTo(storedAuthHolder)); - Mockito.verify(tokenEnhancer).enhance(token, storedAuthentication); - Mockito.verify(tokenRepository).saveAccessToken(token); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(tokenEnhancer).enhance(token, storedAuthentication); + verify(tokenRepository).saveAccessToken(token); + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + } - + @Test public void refreshAccessToken_requestingSameScope() { - OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getScope(), equalTo(storedScope)); } @Test public void refreshAccessToken_requestingLessScope() { - - Set lessScope = Sets.newHashSet("openid", "profile"); + Set lessScope = newHashSet("openid", "profile"); tokenRequest.setScope(lessScope); OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getScope(), equalTo(lessScope)); } @Test(expected = InvalidScopeException.class) public void refreshAccessToken_requestingMoreScope() { - - Set moreScope = Sets.newHashSet(storedScope); + Set moreScope = newHashSet(storedScope); moreScope.add("address"); moreScope.add("phone"); @@ -484,8 +472,7 @@ public void refreshAccessToken_requestingMoreScope() { */ @Test(expected = InvalidScopeException.class) public void refreshAccessToken_requestingMixedScope() { - - Set mixedScope = Sets.newHashSet("openid", "profile", "address", "phone"); // no email or offline_access + Set mixedScope = newHashSet("openid", "profile", "address", "phone"); // no email or offline_access tokenRequest.setScope(mixedScope); @@ -494,27 +481,25 @@ public void refreshAccessToken_requestingMixedScope() { @Test public void refreshAccessToken_requestingEmptyScope() { - - Set emptyScope = Sets.newHashSet(); + Set emptyScope = newHashSet(); tokenRequest.setScope(emptyScope); OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getScope(), equalTo(storedScope)); } @Test public void refreshAccessToken_requestingNullScope() { - tokenRequest.setScope(null); OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertThat(token.getScope(), equalTo(storedScope)); } @@ -524,10 +509,9 @@ public void refreshAccessToken_requestingNullScope() { */ @Test public void refreshAccessToken_expiration() { - Integer accessTokenValiditySeconds = 3600; - Mockito.when(client.getAccessTokenValiditySeconds()).thenReturn(accessTokenValiditySeconds); + when(client.getAccessTokenValiditySeconds()).thenReturn(accessTokenValiditySeconds); long start = System.currentTimeMillis(); OAuth2AccessTokenEntity token = service.refreshAccessToken(refreshTokenValue, tokenRequest); @@ -537,9 +521,26 @@ public void refreshAccessToken_expiration() { Date lowerBoundAccessTokens = new Date(start + (accessTokenValiditySeconds * 1000L) - DELTA); Date upperBoundAccessTokens = new Date(end + (accessTokenValiditySeconds * 1000L) + DELTA); - Mockito.verify(scopeService, Mockito.atLeastOnce()).removeReservedScopes(Matchers.anySet()); - + verify(scopeService, atLeastOnce()).removeReservedScopes(anySet()); + assertTrue(token.getExpiration().after(lowerBoundAccessTokens) && token.getExpiration().before(upperBoundAccessTokens)); } - + + @Test + public void getAllAccessTokensForUser(){ + when(tokenRepository.getAccessTokensByUserName(userName)).thenReturn(newHashSet(accessToken)); + + Set tokens = service.getAllAccessTokensForUser(userName); + assertEquals(1, tokens.size()); + assertTrue(tokens.contains(accessToken)); + } + + @Test + public void getAllRefreshTokensForUser(){ + when(tokenRepository.getRefreshTokensByUserName(userName)).thenReturn(newHashSet(refreshToken)); + + Set tokens = service.getAllRefreshTokensForUser(userName); + assertEquals(1, tokens.size()); + assertTrue(tokens.contains(refreshToken)); + } } diff --git a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java index 2c1853473b..808d96b581 100644 --- a/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java +++ b/openid-connect-server/src/test/java/org/mitre/oauth2/service/impl/TestDefaultSystemScopeService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -26,9 +27,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; -import org.mockito.stubbing.Answer; import com.google.common.collect.Sets; @@ -52,8 +51,6 @@ public class TestDefaultSystemScopeService { private SystemScope defaultScope2; private SystemScope dynScope1; private SystemScope restrictedScope1; - private SystemScope structuredScope1; - private SystemScope structuredScope1Value; private String defaultDynScope1String = "defaultDynScope1"; private String defaultDynScope2String = "defaultDynScope2"; @@ -61,8 +58,6 @@ public class TestDefaultSystemScopeService { private String defaultScope2String = "defaultScope2"; private String dynScope1String = "dynScope1"; private String restrictedScope1String = "restrictedScope1"; - private String structuredScope1String = "structuredScope1"; - private String structuredValue = "structuredValue"; private Set allScopes; private Set allScopeStrings; @@ -104,21 +99,12 @@ public void prepare() { restrictedScope1 = new SystemScope(restrictedScope1String); restrictedScope1.setRestricted(true); - - // structuredScope1 : structured scope - structuredScope1 = new SystemScope(structuredScope1String); - structuredScope1.setStructured(true); - - // structuredScope1Value : structured scope with value - structuredScope1Value = new SystemScope(structuredScope1String); - structuredScope1Value.setStructured(true); - structuredScope1Value.setStructuredValue(structuredValue); - allScopes = Sets.newHashSet(defaultDynScope1, defaultDynScope2, defaultScope1, defaultScope2, dynScope1, restrictedScope1, structuredScope1); - allScopeStrings = Sets.newHashSet(defaultDynScope1String, defaultDynScope2String, defaultScope1String, defaultScope2String, dynScope1String, restrictedScope1String, structuredScope1String); + allScopes = Sets.newHashSet(defaultDynScope1, defaultDynScope2, defaultScope1, defaultScope2, dynScope1, restrictedScope1); + allScopeStrings = Sets.newHashSet(defaultDynScope1String, defaultDynScope2String, defaultScope1String, defaultScope2String, dynScope1String, restrictedScope1String); - allScopesWithValue = Sets.newHashSet(defaultDynScope1, defaultDynScope2, defaultScope1, defaultScope2, dynScope1, restrictedScope1, structuredScope1, structuredScope1Value); - allScopeStringsWithValue = Sets.newHashSet(defaultDynScope1String, defaultDynScope2String, defaultScope1String, defaultScope2String, dynScope1String, restrictedScope1String, structuredScope1String, structuredScope1String + ":" + structuredValue); + allScopesWithValue = Sets.newHashSet(defaultDynScope1, defaultDynScope2, defaultScope1, defaultScope2, dynScope1, restrictedScope1); + allScopeStringsWithValue = Sets.newHashSet(defaultDynScope1String, defaultDynScope2String, defaultScope1String, defaultScope2String, dynScope1String, restrictedScope1String); Mockito.when(repository.getByValue(defaultDynScope1String)).thenReturn(defaultDynScope1); Mockito.when(repository.getByValue(defaultDynScope2String)).thenReturn(defaultDynScope2); @@ -126,16 +112,6 @@ public void prepare() { Mockito.when(repository.getByValue(defaultScope2String)).thenReturn(defaultScope2); Mockito.when(repository.getByValue(dynScope1String)).thenReturn(dynScope1); Mockito.when(repository.getByValue(restrictedScope1String)).thenReturn(restrictedScope1); - // we re-use this value so we've got to use thenAnswer instead - Mockito.when(repository.getByValue(structuredScope1String)).thenAnswer(new Answer() { - @Override - public SystemScope answer(InvocationOnMock invocation) throws Throwable { - SystemScope s = new SystemScope(structuredScope1String); - s.setStructured(true); - return s; - } - - }); Mockito.when(repository.getAll()).thenReturn(allScopes); } @@ -157,7 +133,7 @@ public void getDefaults() { @Test public void getUnrestricted() { - Set unrestricted = Sets.newHashSet(defaultDynScope1, defaultDynScope2, dynScope1, structuredScope1); + Set unrestricted = Sets.newHashSet(defaultDynScope1, defaultDynScope2, dynScope1); assertThat(service.getUnrestricted(), equalTo(unrestricted)); } @@ -167,9 +143,9 @@ public void getRestricted() { Set restricted = Sets.newHashSet(defaultScope1, defaultScope2, restrictedScope1); assertThat(service.getRestricted(), equalTo(restricted)); - + } - + @Test public void fromStrings() { @@ -210,25 +186,4 @@ public void scopesMatch() { assertThat(service.scopesMatch(expected, actualBad), is(false)); } - @Test - public void scopesMatch_structured() { - Set expected = Sets.newHashSet("foo", "bar", "baz"); - Set actualGood = Sets.newHashSet("foo:value", "baz", "bar"); - Set actualBad = Sets.newHashSet("foo:value", "bar:value"); - - // note: we have to use "thenAnswer" here to mimic the repository not serializing the structuredValue field - Mockito.when(repository.getByValue("foo")).thenAnswer(new Answer() { - @Override - public SystemScope answer(InvocationOnMock invocation) throws Throwable { - SystemScope foo = new SystemScope("foo"); - foo.setStructured(true); - return foo; - } - - }); - - assertThat(service.scopesMatch(expected, actualGood), is(true)); - - assertThat(service.scopesMatch(expected, actualBad), is(false)); - } } diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java new file mode 100644 index 0000000000..fde99f499e --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/assertion/TestJWTBearerAuthenticationProvider.java @@ -0,0 +1,414 @@ +package org.mitre.openid.connect.assertion; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.jwt.signer.service.impl.ClientKeyCacheService; +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.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.common.exceptions.InvalidClientException; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.nimbusds.jose.EncryptionMethod; +import com.nimbusds.jose.JWEAlgorithm; +import com.nimbusds.jose.JWEHeader; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jwt.EncryptedJWT; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.PlainJWT; +import com.nimbusds.jwt.SignedJWT; + +@RunWith(MockitoJUnitRunner.class) +public class TestJWTBearerAuthenticationProvider { + + private static final String CLIENT_ID = "client"; + private static final String SUBJECT = "subject"; + + @Mock + private ClientKeyCacheService validators; + @Mock + private ClientDetailsEntityService clientService; + @Mock + private ConfigurationPropertiesBean config; + + @InjectMocks + private JWTBearerAuthenticationProvider jwtBearerAuthenticationProvider; + + @Mock + private JWTBearerAssertionAuthenticationToken token; + @Mock + private ClientDetailsEntity client; + @Mock + private JWTSigningAndValidationService validator; + + private GrantedAuthority authority1 = new SimpleGrantedAuthority("1"); + private GrantedAuthority authority2 = new SimpleGrantedAuthority("2"); + private GrantedAuthority authority3 = new SimpleGrantedAuthority("3"); + + @Before + public void setup() { + when(clientService.loadClientByClientId(CLIENT_ID)).thenReturn(client); + + when(token.getName()).thenReturn(CLIENT_ID); + + when(client.getClientId()).thenReturn(CLIENT_ID); + when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.NONE); + when(client.getAuthorities()).thenReturn(ImmutableSet.of(authority1, authority2, authority3)); + + when(validators.getValidator(client, JWSAlgorithm.RS256)).thenReturn(validator); + when(validator.validateSignature(any(SignedJWT.class))).thenReturn(true); + + when(config.getIssuer()).thenReturn("http://issuer.com/"); + } + + @Test + public void should_not_support_UsernamePasswordAuthenticationToken() { + assertThat(jwtBearerAuthenticationProvider.supports(UsernamePasswordAuthenticationToken.class), is(false)); + } + + @Test + public void should_support_JWTBearerAssertionAuthenticationToken() { + assertThat(jwtBearerAuthenticationProvider.supports(JWTBearerAssertionAuthenticationToken.class), is(true)); + } + + @Test + public void should_throw_UsernameNotFoundException_when_clientService_throws_InvalidClientException() { + when(clientService.loadClientByClientId(CLIENT_ID)).thenThrow(new InvalidClientException("invalid client")); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(UsernameNotFoundException.class)); + assertThat(thrown.getMessage(), is("Could not find client: " + CLIENT_ID)); + } + + @Test + public void should_throw_AuthenticationServiceException_for_PlainJWT() { + mockPlainJWTAuthAttempt(); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Unsupported JWT type: " + PlainJWT.class.getName())); + } + + @Test + public void should_throw_AuthenticationServiceException_for_EncryptedJWT() { + mockEncryptedJWTAuthAttempt(); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Unsupported JWT type: " + EncryptedJWT.class.getName())); + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_signing_algorithms_do_not_match() { + when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.RS256); + SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.ES384); + when(token.getJwt()).thenReturn(signedJWT); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Client's registered token endpoint signing algorithm (RS256) does not match token's actual algorithm (ES384)")); + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_unsupported_authentication_method_for_SignedJWT() { + List unsupportedAuthMethods = + Arrays.asList(null, AuthMethod.NONE, AuthMethod.SECRET_BASIC, AuthMethod.SECRET_POST); + + for (AuthMethod unsupportedAuthMethod : unsupportedAuthMethods) { + SignedJWT signedJWT = createSignedJWT(); + when(token.getJwt()).thenReturn(signedJWT); + when(client.getTokenEndpointAuthMethod()).thenReturn(unsupportedAuthMethod); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Client does not support this authentication method.")); + } + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_algorithm_for_PRIVATE_KEY_auth_method() { + List invalidAlgorithms = Arrays.asList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512); + + for (JWSAlgorithm algorithm : invalidAlgorithms) { + SignedJWT signedJWT = createSignedJWT(algorithm); + when(token.getJwt()).thenReturn(signedJWT); + when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY); + when(client.getTokenEndpointAuthSigningAlg()).thenReturn(algorithm); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for method")); + } + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_algorithm_for_SECRET_JWT_auth_method() { + List invalidAlgorithms = Arrays.asList( + JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, + JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, + JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512); + + for (JWSAlgorithm algorithm : invalidAlgorithms) { + SignedJWT signedJWT = createSignedJWT(algorithm); + when(token.getJwt()).thenReturn(signedJWT); + when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT); + when(client.getTokenEndpointAuthSigningAlg()).thenReturn(algorithm); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for method")); + } + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_in_heart_mode_and_auth_method_is_not_PRIVATE_KEY() { + SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.HS256); + when(token.getJwt()).thenReturn(signedJWT); + when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.HS256); + when(config.isHeartMode()).thenReturn(true); + when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.SECRET_JWT); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("[HEART mode] Invalid authentication method")); + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_null_validator() { + mockSignedJWTAuthAttempt(); + when(validators.getValidator(any(ClientDetailsEntity.class), any(JWSAlgorithm.class))).thenReturn(null); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Unable to create signature validator for client")); + } + + @Test + public void should_throw_AuthenticationServiceException_for_SignedJWT_when_invalid_signature() { + SignedJWT signedJWT = mockSignedJWTAuthAttempt(); + when(validator.validateSignature(signedJWT)).thenReturn(false); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Signature did not validate for presented JWT authentication.")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_null_issuer() { + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(null).build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Assertion Token Issuer is null")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_not_matching_issuer() { + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer("not matching").build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Issuers do not match")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_null_expiration_time() { + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(null).build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), is("Assertion Token does not have required expiration claim")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_expired_jwt() { + Date expiredDate = new Date(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(500)); + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(expiredDate).build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Assertion Token is expired")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_jwt_valid_in_future() { + Date futureDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(500)); + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(futureDate).notBeforeTime(futureDate).build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Assertion Token not valid until")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_jwt_issued_in_future() { + Date futureDate = new Date(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(500)); + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(futureDate).issueTime(futureDate).build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Assertion Token was issued in the future")); + } + + @Test + public void should_throw_AuthenticationServiceException_when_unmatching_audience() { + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder().issuer(CLIENT_ID).expirationTime(new Date()).audience("invalid").build(); + mockSignedJWTAuthAttempt(jwtClaimsSet); + + Throwable thrown = authenticateAndReturnThrownException(); + + assertThat(thrown, instanceOf(AuthenticationServiceException.class)); + assertThat(thrown.getMessage(), startsWith("Audience does not match")); + } + + @Test + public void should_return_valid_token_when_audience_contains_token_endpoint() { + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder() + .issuer(CLIENT_ID) + .subject(SUBJECT) + .expirationTime(new Date()) + .audience(ImmutableList.of("http://issuer.com/token", "invalid")) + .build(); + JWT jwt = mockSignedJWTAuthAttempt(jwtClaimsSet); + + Authentication authentication = jwtBearerAuthenticationProvider.authenticate(token); + + assertThat(authentication, instanceOf(JWTBearerAssertionAuthenticationToken.class)); + + JWTBearerAssertionAuthenticationToken token = (JWTBearerAssertionAuthenticationToken) authentication; + assertThat(token.getName(), is(SUBJECT)); + assertThat(token.getJwt(), is(jwt)); + assertThat(token.getAuthorities(), hasItems(authority1, authority2, authority3)); + assertThat(token.getAuthorities().size(), is(4)); + } + + @Test + public void should_return_valid_token_when_issuer_does_not_end_with_slash_and_audience_contains_token_endpoint() { + JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder() + .issuer(CLIENT_ID) + .subject(SUBJECT) + .expirationTime(new Date()) + .audience(ImmutableList.of("http://issuer.com/token")) + .build(); + JWT jwt = mockSignedJWTAuthAttempt(jwtClaimsSet); + when(config.getIssuer()).thenReturn("http://issuer.com/"); + + Authentication authentication = jwtBearerAuthenticationProvider.authenticate(token); + + assertThat(authentication, instanceOf(JWTBearerAssertionAuthenticationToken.class)); + + JWTBearerAssertionAuthenticationToken token = (JWTBearerAssertionAuthenticationToken) authentication; + assertThat(token.getName(), is(SUBJECT)); + assertThat(token.getJwt(), is(jwt)); + assertThat(token.getAuthorities(), hasItems(authority1, authority2, authority3)); + assertThat(token.getAuthorities().size(), is(4)); + } + + private void mockPlainJWTAuthAttempt() { + PlainJWT plainJWT = new PlainJWT(createJwtClaimsSet()); + when(token.getJwt()).thenReturn(plainJWT); + } + + private void mockEncryptedJWTAuthAttempt() { + JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.A128GCMKW, EncryptionMethod.A256GCM).build(); + EncryptedJWT encryptedJWT = new EncryptedJWT(jweHeader, createJwtClaimsSet()); + when(token.getJwt()).thenReturn(encryptedJWT); + } + + private SignedJWT mockSignedJWTAuthAttempt() { + return mockSignedJWTAuthAttempt(createJwtClaimsSet()); + } + + private SignedJWT mockSignedJWTAuthAttempt(JWTClaimsSet jwtClaimsSet) { + SignedJWT signedJWT = createSignedJWT(JWSAlgorithm.RS256, jwtClaimsSet); + when(token.getJwt()).thenReturn(signedJWT); + when(client.getTokenEndpointAuthMethod()).thenReturn(AuthMethod.PRIVATE_KEY); + when(client.getTokenEndpointAuthSigningAlg()).thenReturn(JWSAlgorithm.RS256); + return signedJWT; + } + + private Throwable authenticateAndReturnThrownException() { + try { + jwtBearerAuthenticationProvider.authenticate(token); + } catch (Throwable throwable) { + return throwable; + } + throw new AssertionError("No exception thrown when expected"); + } + + private SignedJWT createSignedJWT() { + return createSignedJWT(JWSAlgorithm.RS256); + } + + private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm) { + JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).build(); + JWTClaimsSet claims = createJwtClaimsSet(); + + return new SignedJWT(jwsHeader, claims); + } + + private SignedJWT createSignedJWT(JWSAlgorithm jwsAlgorithm, JWTClaimsSet jwtClaimsSet) { + JWSHeader jwsHeader = new JWSHeader.Builder(jwsAlgorithm).build(); + + return new SignedJWT(jwsHeader, jwtClaimsSet); + } + + private JWTClaimsSet createJwtClaimsSet() { + return new JWTClaimsSet.Builder() + .issuer(CLIENT_ID) + .expirationTime(new Date()) + .audience("http://issuer.com/") + .build(); + } + +} diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/config/TestJsonMessageSource.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/config/TestJsonMessageSource.java new file mode 100644 index 0000000000..04d7735d2f --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/config/TestJsonMessageSource.java @@ -0,0 +1,50 @@ +package org.mitre.openid.connect.config; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import java.text.MessageFormat; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@RunWith(MockitoJUnitRunner.class) +public class TestJsonMessageSource { + + @InjectMocks + private JsonMessageSource jsonMessageSource; + + @Spy + private ConfigurationPropertiesBean config; + + private Locale localeThatHasAFile = new Locale("en"); + + private Locale localeThatDoesNotHaveAFile = new Locale("xx"); + + @Before + public void setup() { + //test message files are located in test/resources/js/locale/ + Resource resource = new ClassPathResource("/resources/js/locale/"); + jsonMessageSource.setBaseDirectory(resource); + } + + @Test + public void verifyWhenLocaleExists_canResolveCode() { + MessageFormat mf = jsonMessageSource.resolveCode("testAttribute", localeThatHasAFile); + assertEquals(mf.getLocale().getLanguage(), "en"); + assertEquals(mf.toPattern(), "testValue"); + } + + @Test + public void verifyWhenLocaleDoesNotExist_cannotResolveCode() { + MessageFormat mf = jsonMessageSource.resolveCode("test", localeThatDoesNotHaveAFile); + assertNull(mf); + } +} diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java index 95a8049d8e..8524145f0b 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultApprovedSiteService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,13 +17,18 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mockito.Matchers.any; + import java.util.HashSet; +import java.util.List; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.repository.OAuth2TokenRepository; import org.mitre.openid.connect.model.ApprovedSite; import org.mitre.openid.connect.repository.ApprovedSiteRepository; import org.mitre.openid.connect.service.ApprovedSiteService; @@ -33,10 +39,9 @@ import org.mockito.runners.MockitoJUnitRunner; import org.springframework.test.annotation.Rollback; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; -import static org.mockito.Matchers.any; - import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -53,6 +58,9 @@ public class TestDefaultApprovedSiteService { @Mock private ApprovedSiteRepository repository; + @Mock + private OAuth2TokenRepository tokenRepository; + @Mock private StatsService statsService; @@ -97,6 +105,8 @@ public void prepare() { public void clearApprovedSitesForClient_success() { Set setToReturn = Sets.newHashSet(site2, site3); Mockito.when(repository.getByClientId(client.getClientId())).thenReturn(setToReturn); + List tokens = ImmutableList.of(); + Mockito.when(tokenRepository.getAccessTokensForApprovedSite(any(ApprovedSite.class))).thenReturn(tokens); service.clearApprovedSitesForClient(client); diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java index 454b536854..79656cdddc 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultBlacklistedSiteService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java new file mode 100644 index 0000000000..01405474c0 --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultOIDCTokenService.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * 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.impl; + +import java.util.Date; + +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TestDefaultOIDCTokenService { + private static final String CLIENT_ID = "client"; + private static final String KEY_ID = "key"; + + private ConfigurationPropertiesBean configBean = new ConfigurationPropertiesBean(); + private ClientDetailsEntity client = new ClientDetailsEntity(); + private OAuth2AccessTokenEntity accessToken = new OAuth2AccessTokenEntity(); + private OAuth2Request request = new OAuth2Request(CLIENT_ID) { }; + + @Mock + private JWTSigningAndValidationService jwtService; + + @Before + public void prepare() { + configBean.setIssuer("https://auth.example.org/"); + + client.setClientId(CLIENT_ID); + Mockito.when(jwtService.getDefaultSigningAlgorithm()).thenReturn(JWSAlgorithm.RS256); + Mockito.when(jwtService.getDefaultSignerKeyId()).thenReturn(KEY_ID); + } + + @Test + public void invokesCustomClaimsHook() throws java.text.ParseException { + DefaultOIDCTokenService s = new DefaultOIDCTokenService() { + @Override + protected void addCustomIdTokenClaims(JWTClaimsSet.Builder idClaims, ClientDetailsEntity client, OAuth2Request request, + String sub, OAuth2AccessTokenEntity accessToken) { + idClaims.claim("test", "foo"); + } + }; + configure(s); + + JWT token = s.createIdToken(client, request, new Date(), "sub", accessToken); + Assert.assertEquals("foo", token.getJWTClaimsSet().getClaim("test")); + } + + + private void configure(DefaultOIDCTokenService s) { + s.setConfigBean(configBean); + s.setJwtService(jwtService); + } +} diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultStatsService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultStatsService.java index a46890d8d9..b5c1ae6b32 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultStatsService.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultStatsService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -23,7 +24,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.openid.connect.model.ApprovedSite; import org.mitre.openid.connect.service.ApprovedSiteService; import org.mockito.InjectMocks; @@ -71,9 +71,6 @@ public class TestDefaultStatsService { @Mock private ApprovedSiteService approvedSiteService; - @Mock - private ClientDetailsEntityService clientService; - @InjectMocks private DefaultStatsService service = new DefaultStatsService(); @@ -84,7 +81,7 @@ public class TestDefaultStatsService { @Before public void prepare() { - Mockito.reset(approvedSiteService, clientService); + Mockito.reset(approvedSiteService); Mockito.when(ap1.getUserId()).thenReturn(userId1); Mockito.when(ap1.getClientId()).thenReturn(clientId1); @@ -111,11 +108,10 @@ public void prepare() { Mockito.when(client3.getId()).thenReturn(3L); Mockito.when(client4.getId()).thenReturn(4L); - Mockito.when(clientService.getAllClients()).thenReturn(Sets.newHashSet(client1, client2, client3, client4)); - Mockito.when(clientService.loadClientByClientId(clientId1)).thenReturn(client1); - Mockito.when(clientService.loadClientByClientId(clientId2)).thenReturn(client2); - Mockito.when(clientService.loadClientByClientId(clientId3)).thenReturn(client3); - Mockito.when(clientService.loadClientByClientId(clientId4)).thenReturn(client4); + Mockito.when(approvedSiteService.getByClientId(clientId1)).thenReturn(Sets.newHashSet(ap1, ap2)); + Mockito.when(approvedSiteService.getByClientId(clientId2)).thenReturn(Sets.newHashSet(ap3)); + Mockito.when(approvedSiteService.getByClientId(clientId3)).thenReturn(Sets.newHashSet(ap4)); + Mockito.when(approvedSiteService.getByClientId(clientId4)).thenReturn(Sets.newHashSet()); } @Test @@ -139,37 +135,13 @@ public void calculateSummaryStats() { assertThat(stats.get("clientCount"), is(3)); } - @Test - public void calculateByClientId_empty() { - - Mockito.when(approvedSiteService.getAll()).thenReturn(new HashSet()); - - Map stats = service.getByClientId(); - - assertThat(stats.get(1L), is(0)); - assertThat(stats.get(2L), is(0)); - assertThat(stats.get(3L), is(0)); - assertThat(stats.get(4L), is(0)); - } - - @Test - public void calculateByClientId() { - - Map stats = service.getByClientId(); - - assertThat(stats.get(1L), is(2)); - assertThat(stats.get(2L), is(1)); - assertThat(stats.get(3L), is(1)); - assertThat(stats.get(4L), is(0)); - } - @Test public void countForClientId() { - - assertThat(service.getCountForClientId(1L), is(2)); - assertThat(service.getCountForClientId(2L), is(1)); - assertThat(service.getCountForClientId(3L), is(1)); - assertThat(service.getCountForClientId(4L), is(0)); + // stats for ap1..ap4 + assertThat(service.getCountForClientId(clientId1).getApprovedSiteCount(), is(2)); + assertThat(service.getCountForClientId(clientId2).getApprovedSiteCount(), is(1)); + assertThat(service.getCountForClientId(clientId3).getApprovedSiteCount(), is(1)); + assertThat(service.getCountForClientId(clientId4).getApprovedSiteCount(), is(0)); } @Test diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java index 2870eafa10..e5e7070832 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultUserInfoService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service.impl; diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java index dabe482c22..40e8d65087 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestDefaultWhitelistedSiteService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_0.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_0.java index 25de4a6e0b..74c275939f 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_0.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_0.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,11 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.isNull; + import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; @@ -65,6 +71,7 @@ import org.springframework.security.oauth2.provider.OAuth2Request; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.nimbusds.jwt.JWTParser; @@ -72,11 +79,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.isA; -import static org.mockito.Matchers.isNull; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -123,7 +125,7 @@ public class TestMITREidDataService_1_0 { @InjectMocks private MITREidDataService_1_0 dataService; - + private DateFormatter formatter; @Before @@ -139,10 +141,10 @@ public int compare(OAuth2RefreshTokenEntity entity1, OAuth2RefreshTokenEntity en return entity1.getId().compareTo(entity2.getId()); } } - + @Test public void testImportRefreshTokens() throws IOException, ParseException { - Date expirationDate1 = formatter.parse("2014-09-10T22:49:44.090+0000", Locale.ENGLISH); + Date expirationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); @@ -157,7 +159,7 @@ public void testImportRefreshTokens() throws IOException, ParseException { token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); token1.setAuthenticationHolder(mockedAuthHolder1); - Date expirationDate2 = formatter.parse("2015-01-07T18:31:50.079+0000", Locale.ENGLISH); + Date expirationDate2 = formatter.parse("2015-01-07T18:31:50.079+00:00", Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); @@ -182,9 +184,9 @@ public void testImportRefreshTokens() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + " ]" + @@ -236,18 +238,18 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr //2 times for token, 2 times to update client, 2 times to update authHolder verify(tokenRepository, times(6)).saveRefreshToken(capturedRefreshTokens.capture()); - List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); - Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); + List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); + Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); - assertThat(savedRefreshTokens.size(), is(2)); + assertThat(savedRefreshTokens.size(), is(2)); - assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); - assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); - assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); + assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); - assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); - assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); - assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); + assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); } private class accessTokenIdComparator implements Comparator { @@ -259,7 +261,7 @@ public int compare(OAuth2AccessTokenEntity entity1, OAuth2AccessTokenEntity enti @Test public void testImportAccessTokens() throws IOException, ParseException { - Date expirationDate1 = formatter.parse("2014-09-10T22:49:44.090+0000", Locale.ENGLISH); + Date expirationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); @@ -276,7 +278,7 @@ public void testImportAccessTokens() throws IOException, ParseException { token1.setScope(ImmutableSet.of("id-token")); token1.setTokenType("Bearer"); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -294,7 +296,6 @@ public void testImportAccessTokens() throws IOException, ParseException { token2.setExpiration(expirationDate2); token2.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ")); token2.setAuthenticationHolder(mockedAuthHolder2); - token2.setIdToken(token1); token2.setRefreshToken(mockRefreshToken2); token2.setScope(ImmutableSet.of("openid", "offline_access", "email", "profile")); token2.setTokenType("Bearer"); @@ -309,10 +310,10 @@ public void testImportAccessTokens() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.ACCESSTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"refreshTokenId\":null,\"idTokenId\":null,\"scope\":[\"id-token\"],\"type\":\"Bearer\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"refreshTokenId\":1,\"idTokenId\":1,\"scope\":[\"openid\",\"offline_access\",\"email\",\"profile\"],\"type\":\"Bearer\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ\"}" + @@ -364,21 +365,21 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr } }); dataService.importData(reader); - //2 times for token, 2 times to update client, 2 times to update authHolder, 2 times to update id token, 2 times to update refresh token - verify(tokenRepository, times(8)).saveAccessToken(capturedAccessTokens.capture()); + //2 times for token, 2 times to update client, 2 times to update authHolder, 1 times to update refresh token + verify(tokenRepository, times(7)).saveAccessToken(capturedAccessTokens.capture()); - List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); - Collections.sort(savedAccessTokens, new accessTokenIdComparator()); + List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); + Collections.sort(savedAccessTokens, new accessTokenIdComparator()); - assertThat(savedAccessTokens.size(), is(2)); + assertThat(savedAccessTokens.size(), is(2)); - assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); - assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); - assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); + assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); - assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); - assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); - assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); + assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); } @@ -575,8 +576,8 @@ public WhitelistedSite answer(InvocationOnMock invocation) throws Throwable { @Test public void testImportGrants() throws IOException, ParseException { - Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+0000", Locale.ENGLISH); - Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+0000", Locale.ENGLISH); + Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); + Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+00:00", Locale.ENGLISH); OAuth2AccessTokenEntity mockToken1 = mock(OAuth2AccessTokenEntity.class); when(mockToken1.getId()).thenReturn(1L); @@ -588,11 +589,11 @@ public void testImportGrants() throws IOException, ParseException { site1.setAccessDate(accessDate1); site1.setUserId("user1"); site1.setAllowedScopes(ImmutableSet.of("openid", "phone")); - site1.setApprovedAccessTokens(ImmutableSet.of(mockToken1)); + when(mockToken1.getApprovedSite()).thenReturn(site1); - Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+0000", Locale.ENGLISH); - Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+0000", Locale.ENGLISH); - Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+0000", Locale.ENGLISH); + Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+00:00", Locale.ENGLISH); + Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+00:00", Locale.ENGLISH); + Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+00:00", Locale.ENGLISH); ApprovedSite site2 = new ApprovedSite(); site2.setId(2L); @@ -613,11 +614,11 @@ public void testImportGrants() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.GRANTS + "\": [" + - "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+0000\",\"accessDate\":\"2014-09-10T23:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+00:00\",\"accessDate\":\"2014-09-10T23:49:44.090+00:00\"," + "\"userId\":\"user1\",\"whitelistedSiteId\":null,\"allowedScopes\":[\"openid\",\"phone\"], \"whitelistedSiteId\":1," + "\"approvedAccessTokens\":[1]}," + - "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+0000\",\"accessDate\":\"2014-09-11T20:49:44.090+0000\"," - + "\"timeoutDate\":\"2014-10-01T20:49:44.090+0000\",\"userId\":\"user2\"," + "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+00:00\",\"accessDate\":\"2014-09-11T20:49:44.090+00:00\"," + + "\"timeoutDate\":\"2014-10-01T20:49:44.090+00:00\",\"userId\":\"user2\"," + "\"allowedScopes\":[\"openid\",\"offline_access\",\"email\",\"profile\"]}" + " ]" + @@ -665,28 +666,27 @@ public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwa return _token; } }); + when(tokenRepository.getAccessTokensForApprovedSite(site1)).thenReturn(Lists.newArrayList(mockToken1)); dataService.importData(reader); //2 for sites, 1 for updating access token ref on #1 verify(approvedSiteRepository, times(3)).save(capturedApprovedSites.capture()); - List savedSites = new ArrayList(fakeDb.values()); + List savedSites = new ArrayList(fakeDb.values()); - assertThat(savedSites.size(), is(2)); + assertThat(savedSites.size(), is(2)); - assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); - assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); - assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); - assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); - assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); - assertThat(savedSites.get(0).getApprovedAccessTokens().size(), equalTo(site1.getApprovedAccessTokens().size())); + assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); + assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); + assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); + assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); + assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); - assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); - assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); - assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); - assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); - assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); - assertThat(savedSites.get(1).getApprovedAccessTokens().size(), equalTo(site2.getApprovedAccessTokens().size())); + assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); + assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); + assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); + assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); + assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); } @Test @@ -831,7 +831,7 @@ public void testImportSystemScopes() throws IOException { @Test public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -854,7 +854,7 @@ public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); token1.setAuthenticationHolder(holder1); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -893,9 +893,9 @@ public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException " ]," + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + " ]" + diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_1.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_1.java index 71a9dfd76f..cfeb43a6f7 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_1.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_1.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,6 +17,11 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.isNull; + import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; @@ -72,11 +78,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.isA; -import static org.mockito.Matchers.isNull; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -144,7 +145,7 @@ public int compare(OAuth2RefreshTokenEntity entity1, OAuth2RefreshTokenEntity en @Test public void testImportRefreshTokens() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -160,7 +161,7 @@ public void testImportRefreshTokens() throws IOException, ParseException { token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); token1.setAuthenticationHolder(mockedAuthHolder1); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -186,9 +187,9 @@ public void testImportRefreshTokens() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + " ]" + @@ -240,18 +241,18 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr //2 times for token, 2 times to update client, 2 times to update authHolder verify(tokenRepository, times(6)).saveRefreshToken(capturedRefreshTokens.capture()); - List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); - Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); + List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); + Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); - assertThat(savedRefreshTokens.size(), is(2)); + assertThat(savedRefreshTokens.size(), is(2)); - assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); - assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); - assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); + assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); - assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); - assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); - assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); + assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); } private class accessTokenIdComparator implements Comparator { @@ -263,7 +264,7 @@ public int compare(OAuth2AccessTokenEntity entity1, OAuth2AccessTokenEntity enti @Test public void testImportAccessTokens() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -281,7 +282,7 @@ public void testImportAccessTokens() throws IOException, ParseException { token1.setScope(ImmutableSet.of("id-token")); token1.setTokenType("Bearer"); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -299,7 +300,6 @@ public void testImportAccessTokens() throws IOException, ParseException { token2.setExpiration(expirationDate2); token2.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ")); token2.setAuthenticationHolder(mockedAuthHolder2); - token2.setIdToken(token1); token2.setRefreshToken(mockRefreshToken2); token2.setScope(ImmutableSet.of("openid", "offline_access", "email", "profile")); token2.setTokenType("Bearer"); @@ -314,10 +314,10 @@ public void testImportAccessTokens() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.ACCESSTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"refreshTokenId\":null,\"idTokenId\":null,\"scope\":[\"id-token\"],\"type\":\"Bearer\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"refreshTokenId\":1,\"idTokenId\":1,\"scope\":[\"openid\",\"offline_access\",\"email\",\"profile\"],\"type\":\"Bearer\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ\"}" + @@ -369,21 +369,21 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr } }); dataService.importData(reader); - //2 times for token, 2 times to update client, 2 times to update authHolder, 2 times to update id token, 2 times to update refresh token - verify(tokenRepository, times(8)).saveAccessToken(capturedAccessTokens.capture()); + //2 times for token, 2 times to update client, 2 times to update authHolder, 1 times to update refresh token + verify(tokenRepository, times(7)).saveAccessToken(capturedAccessTokens.capture()); - List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); - Collections.sort(savedAccessTokens, new accessTokenIdComparator()); + List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); + Collections.sort(savedAccessTokens, new accessTokenIdComparator()); - assertThat(savedAccessTokens.size(), is(2)); + assertThat(savedAccessTokens.size(), is(2)); - assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); - assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); - assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); + assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); - assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); - assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); - assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); + assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); } @Test @@ -579,8 +579,8 @@ public WhitelistedSite answer(InvocationOnMock invocation) throws Throwable { @Test public void testImportGrants() throws IOException, ParseException { - Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+0000", Locale.ENGLISH); - Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+0000", Locale.ENGLISH); + Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); + Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+00:00", Locale.ENGLISH); OAuth2AccessTokenEntity mockToken1 = mock(OAuth2AccessTokenEntity.class); when(mockToken1.getId()).thenReturn(1L); @@ -592,11 +592,11 @@ public void testImportGrants() throws IOException, ParseException { site1.setAccessDate(accessDate1); site1.setUserId("user1"); site1.setAllowedScopes(ImmutableSet.of("openid", "phone")); - site1.setApprovedAccessTokens(ImmutableSet.of(mockToken1)); + when(mockToken1.getApprovedSite()).thenReturn(site1); - Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+0000", Locale.ENGLISH); - Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+0000", Locale.ENGLISH); - Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+0000", Locale.ENGLISH); + Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+00:00", Locale.ENGLISH); + Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+00:00", Locale.ENGLISH); + Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+00:00", Locale.ENGLISH); ApprovedSite site2 = new ApprovedSite(); site2.setId(2L); @@ -617,11 +617,11 @@ public void testImportGrants() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.GRANTS + "\": [" + - "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+0000\",\"accessDate\":\"2014-09-10T23:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+00:00\",\"accessDate\":\"2014-09-10T23:49:44.090+00:00\"," + "\"userId\":\"user1\",\"whitelistedSiteId\":null,\"allowedScopes\":[\"openid\",\"phone\"], \"whitelistedSiteId\":1," + "\"approvedAccessTokens\":[1]}," + - "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+0000\",\"accessDate\":\"2014-09-11T20:49:44.090+0000\"," - + "\"timeoutDate\":\"2014-10-01T20:49:44.090+0000\",\"userId\":\"user2\"," + "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+00:00\",\"accessDate\":\"2014-09-11T20:49:44.090+00:00\"," + + "\"timeoutDate\":\"2014-10-01T20:49:44.090+00:00\",\"userId\":\"user2\"," + "\"allowedScopes\":[\"openid\",\"offline_access\",\"email\",\"profile\"]}" + " ]" + @@ -674,23 +674,21 @@ public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwa //2 for sites, 1 for updating access token ref on #1 verify(approvedSiteRepository, times(3)).save(capturedApprovedSites.capture()); - List savedSites = new ArrayList(fakeDb.values()); + List savedSites = new ArrayList(fakeDb.values()); - assertThat(savedSites.size(), is(2)); + assertThat(savedSites.size(), is(2)); - assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); - assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); - assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); - assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); - assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); - assertThat(savedSites.get(0).getApprovedAccessTokens().size(), equalTo(site1.getApprovedAccessTokens().size())); + assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); + assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); + assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); + assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); + assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); - assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); - assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); - assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); - assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); - assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); - assertThat(savedSites.get(1).getApprovedAccessTokens().size(), equalTo(site2.getApprovedAccessTokens().size())); + assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); + assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); + assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); + assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); + assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); } @Test @@ -785,8 +783,6 @@ public void testImportSystemScopes() throws IOException { scope3.setRestricted(false); scope3.setDefaultScope(true); scope3.setIcon("road"); - scope3.setStructured(true); - scope3.setStructuredParamDescription("Structured Parameter"); String configJson = "{" + "\"" + MITREidDataService.CLIENTS + "\": [], " + @@ -800,7 +796,7 @@ public void testImportSystemScopes() throws IOException { "{\"id\":1,\"description\":\"Scope 1\",\"icon\":\"glass\",\"value\":\"scope1\",\"allowDynReg\":false,\"defaultScope\":false}," + "{\"id\":2,\"description\":\"Scope 2\",\"icon\":\"ball\",\"value\":\"scope2\",\"allowDynReg\":true,\"defaultScope\":false}," + - "{\"id\":3,\"description\":\"Scope 3\",\"icon\":\"road\",\"value\":\"scope3\",\"allowDynReg\":true,\"defaultScope\":true,\"structured\":true,\"structuredParameter\":\"Structured Parameter\"}" + + "{\"id\":3,\"description\":\"Scope 3\",\"icon\":\"road\",\"value\":\"scope3\",\"allowDynReg\":true,\"defaultScope\":true}" + " ]" + "}"; @@ -820,30 +816,24 @@ public void testImportSystemScopes() throws IOException { assertThat(savedScopes.get(0).getIcon(), equalTo(scope1.getIcon())); assertThat(savedScopes.get(0).isDefaultScope(), equalTo(scope1.isDefaultScope())); assertThat(savedScopes.get(0).isRestricted(), equalTo(scope1.isRestricted())); - assertThat(savedScopes.get(0).isStructured(), equalTo(scope1.isStructured())); - assertThat(savedScopes.get(0).getStructuredParamDescription(), equalTo(scope1.getStructuredParamDescription())); assertThat(savedScopes.get(1).getValue(), equalTo(scope2.getValue())); assertThat(savedScopes.get(1).getDescription(), equalTo(scope2.getDescription())); assertThat(savedScopes.get(1).getIcon(), equalTo(scope2.getIcon())); assertThat(savedScopes.get(1).isDefaultScope(), equalTo(scope2.isDefaultScope())); assertThat(savedScopes.get(1).isRestricted(), equalTo(scope2.isRestricted())); - assertThat(savedScopes.get(1).isStructured(), equalTo(scope2.isStructured())); - assertThat(savedScopes.get(1).getStructuredParamDescription(), equalTo(scope2.getStructuredParamDescription())); assertThat(savedScopes.get(2).getValue(), equalTo(scope3.getValue())); assertThat(savedScopes.get(2).getDescription(), equalTo(scope3.getDescription())); assertThat(savedScopes.get(2).getIcon(), equalTo(scope3.getIcon())); assertThat(savedScopes.get(2).isDefaultScope(), equalTo(scope3.isDefaultScope())); assertThat(savedScopes.get(2).isRestricted(), equalTo(scope3.isRestricted())); - assertThat(savedScopes.get(2).isStructured(), equalTo(scope3.isStructured())); - assertThat(savedScopes.get(2).getStructuredParamDescription(), equalTo(scope3.getStructuredParamDescription())); } @Test public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -866,7 +856,7 @@ public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); token1.setAuthenticationHolder(holder1); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -905,9 +895,9 @@ public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException " ]," + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + " ]" + diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_2.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_2.java index 0454f2b1ce..598471126f 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_2.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_2.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -16,9 +15,13 @@ *******************************************************************************/ package org.mitre.openid.connect.service.impl; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.isNull; + import java.io.IOException; import java.io.StringReader; -import java.io.StringWriter; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; @@ -62,47 +65,32 @@ import org.slf4j.LoggerFactory; import org.springframework.format.annotation.DateTimeFormat.ISO; import org.springframework.format.datetime.DateFormatter; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.gson.JsonArray; import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import com.nimbusds.jwt.JWTParser; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.isA; -import static org.mockito.Matchers.isNull; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; @RunWith(MockitoJUnitRunner.class) @SuppressWarnings(value = {"rawtypes", "unchecked"}) public class TestMITREidDataService_1_2 { - + private static Logger logger = LoggerFactory.getLogger(TestMITREidDataService_1_2.class); @Mock @@ -149,121 +137,6 @@ public void prepare() { Mockito.reset(clientRepository, approvedSiteRepository, authHolderRepository, tokenRepository, sysScopeRepository, wlSiteRepository, blSiteRepository); } - @Test - public void testExportRefreshTokens() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; - Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); - - ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); - when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); - - AuthenticationHolderEntity mockedAuthHolder1 = mock(AuthenticationHolderEntity.class); - when(mockedAuthHolder1.getId()).thenReturn(1L); - - OAuth2RefreshTokenEntity token1 = new OAuth2RefreshTokenEntity(); - token1.setId(1L); - token1.setClient(mockedClient1); - token1.setExpiration(expirationDate1); - token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); - token1.setAuthenticationHolder(mockedAuthHolder1); - - String expiration2 = "2015-01-07T18:31:50.079+0000"; - Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); - - ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); - when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); - - AuthenticationHolderEntity mockedAuthHolder2 = mock(AuthenticationHolderEntity.class); - when(mockedAuthHolder2.getId()).thenReturn(2L); - - OAuth2RefreshTokenEntity token2 = new OAuth2RefreshTokenEntity(); - token2.setId(2L); - token2.setClient(mockedClient2); - token2.setExpiration(expirationDate2); - token2.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.")); - token2.setAuthenticationHolder(mockedAuthHolder2); - - Set allRefreshTokens = ImmutableSet.of(token1, token2); - - Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(allRefreshTokens); - Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - - // check our refresh token list (this test) - JsonArray refreshTokens = config.get(MITREidDataService.REFRESHTOKENS).getAsJsonArray(); - - assertThat(refreshTokens.size(), is(2)); - // check for both of our refresh tokens in turn - Set checked = new HashSet<>(); - for (JsonElement e : refreshTokens) { - assertThat(e.isJsonObject(), is(true)); - JsonObject token = e.getAsJsonObject(); - - OAuth2RefreshTokenEntity compare = null; - if (token.get("id").getAsLong() == token1.getId()) { - compare = token1; - } else if (token.get("id").getAsLong() == token2.getId()) { - compare = token2; - } - - if (compare == null) { - fail("Could not find matching id: " + token.get("id").getAsString()); - } else { - assertThat(token.get("id").getAsLong(), equalTo(compare.getId())); - assertThat(token.get("clientId").getAsString(), equalTo(compare.getClient().getClientId())); - assertThat(token.get("expiration").getAsString(), equalTo(formatter.print(compare.getExpiration(), Locale.ENGLISH))); - assertThat(token.get("value").getAsString(), equalTo(compare.getValue())); - assertThat(token.get("authenticationHolderId").getAsLong(), equalTo(compare.getAuthenticationHolder().getId())); - checked.add(compare); - } - } - // make sure all of our refresh tokens were found - assertThat(checked.containsAll(allRefreshTokens), is(true)); - } - private class refreshTokenIdComparator implements Comparator { @Override public int compare(OAuth2RefreshTokenEntity entity1, OAuth2RefreshTokenEntity entity2) { @@ -274,7 +147,7 @@ public int compare(OAuth2RefreshTokenEntity entity1, OAuth2RefreshTokenEntity en @Test public void testImportRefreshTokens() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -290,7 +163,7 @@ public void testImportRefreshTokens() throws IOException, ParseException { token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); token1.setAuthenticationHolder(mockedAuthHolder1); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -316,9 +189,9 @@ public void testImportRefreshTokens() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + " ]" + @@ -370,155 +243,18 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr //2 times for token, 2 times to update client, 2 times to update authHolder verify(tokenRepository, times(6)).saveRefreshToken(capturedRefreshTokens.capture()); - List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); - Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); - - assertThat(savedRefreshTokens.size(), is(2)); - - assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); - assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); - assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); - - assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); - assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); - assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); - } - - @Test - public void testExportAccessTokens() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; - Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); - - ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); - when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); - - AuthenticationHolderEntity mockedAuthHolder1 = mock(AuthenticationHolderEntity.class); - when(mockedAuthHolder1.getId()).thenReturn(1L); - - OAuth2AccessTokenEntity token1 = new OAuth2AccessTokenEntity(); - token1.setId(1L); - token1.setClient(mockedClient1); - token1.setExpiration(expirationDate1); - token1.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0")); - token1.setAuthenticationHolder(mockedAuthHolder1); - token1.setScope(ImmutableSet.of("id-token")); - token1.setTokenType("Bearer"); - - String expiration2 = "2015-01-07T18:31:50.079+0000"; - Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); - - ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); - when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); - - AuthenticationHolderEntity mockedAuthHolder2 = mock(AuthenticationHolderEntity.class); - when(mockedAuthHolder2.getId()).thenReturn(2L); - - OAuth2RefreshTokenEntity mockRefreshToken2 = mock(OAuth2RefreshTokenEntity.class); - when(mockRefreshToken2.getId()).thenReturn(1L); + List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); + Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); - OAuth2AccessTokenEntity token2 = new OAuth2AccessTokenEntity(); - token2.setId(2L); - token2.setClient(mockedClient2); - token2.setExpiration(expirationDate2); - token2.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ")); - token2.setAuthenticationHolder(mockedAuthHolder2); - token2.setIdToken(token1); - token2.setRefreshToken(mockRefreshToken2); - token2.setScope(ImmutableSet.of("openid", "offline_access", "email", "profile")); - token2.setTokenType("Bearer"); + assertThat(savedRefreshTokens.size(), is(2)); - Set allAccessTokens = ImmutableSet.of(token1, token2); - - Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(allAccessTokens); - Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - - // check our access token list (this test) - JsonArray accessTokens = config.get(MITREidDataService.ACCESSTOKENS).getAsJsonArray(); - - assertThat(accessTokens.size(), is(2)); - // check for both of our access tokens in turn - Set checked = new HashSet<>(); - for (JsonElement e : accessTokens) { - assertTrue(e.isJsonObject()); - JsonObject token = e.getAsJsonObject(); - - OAuth2AccessTokenEntity compare = null; - if (token.get("id").getAsLong() == token1.getId().longValue()) { - compare = token1; - } else if (token.get("id").getAsLong() == token2.getId().longValue()) { - compare = token2; - } + assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); - if (compare == null) { - fail("Could not find matching id: " + token.get("id").getAsString()); - } else { - assertThat(token.get("id").getAsLong(), equalTo(compare.getId())); - assertThat(token.get("clientId").getAsString(), equalTo(compare.getClient().getClientId())); - assertThat(token.get("expiration").getAsString(), equalTo(formatter.print(compare.getExpiration(), Locale.ENGLISH))); - assertThat(token.get("value").getAsString(), equalTo(compare.getValue())); - assertThat(token.get("type").getAsString(), equalTo(compare.getTokenType())); - assertThat(token.get("authenticationHolderId").getAsLong(), equalTo(compare.getAuthenticationHolder().getId())); - assertTrue(token.get("scope").isJsonArray()); - assertThat(jsonArrayToStringSet(token.getAsJsonArray("scope")), equalTo(compare.getScope())); - if(token.get("idTokenId").isJsonNull()) { - assertNull(compare.getIdToken()); - } else { - assertThat(token.get("idTokenId").getAsLong(), equalTo(compare.getIdToken().getId())); - } - if(token.get("refreshTokenId").isJsonNull()) { - assertNull(compare.getIdToken()); - } else { - assertThat(token.get("refreshTokenId").getAsLong(), equalTo(compare.getRefreshToken().getId())); - } - checked.add(compare); - } - } - // make sure all of our access tokens were found - assertThat(checked.containsAll(allAccessTokens), is(true)); + assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); } private class accessTokenIdComparator implements Comparator { @@ -530,7 +266,7 @@ public int compare(OAuth2AccessTokenEntity entity1, OAuth2AccessTokenEntity enti @Test public void testImportAccessTokens() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -548,7 +284,7 @@ public void testImportAccessTokens() throws IOException, ParseException { token1.setScope(ImmutableSet.of("id-token")); token1.setTokenType("Bearer"); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -566,7 +302,6 @@ public void testImportAccessTokens() throws IOException, ParseException { token2.setExpiration(expirationDate2); token2.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ")); token2.setAuthenticationHolder(mockedAuthHolder2); - token2.setIdToken(token1); token2.setRefreshToken(mockRefreshToken2); token2.setScope(ImmutableSet.of("openid", "offline_access", "email", "profile")); token2.setTokenType("Bearer"); @@ -581,10 +316,10 @@ public void testImportAccessTokens() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.ACCESSTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"refreshTokenId\":null,\"idTokenId\":null,\"scope\":[\"id-token\"],\"type\":\"Bearer\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"refreshTokenId\":1,\"idTokenId\":1,\"scope\":[\"openid\",\"offline_access\",\"email\",\"profile\"],\"type\":\"Bearer\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ\"}" + @@ -636,126 +371,21 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr } }); dataService.importData(reader); - //2 times for token, 2 times to update client, 2 times to update authHolder, 2 times to update id token, 2 times to update refresh token - verify(tokenRepository, times(8)).saveAccessToken(capturedAccessTokens.capture()); + //2 times for token, 2 times to update client, 2 times to update authHolder, 1 times to update refresh token + verify(tokenRepository, times(7)).saveAccessToken(capturedAccessTokens.capture()); - List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); - Collections.sort(savedAccessTokens, new accessTokenIdComparator()); + List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); + Collections.sort(savedAccessTokens, new accessTokenIdComparator()); - assertThat(savedAccessTokens.size(), is(2)); + assertThat(savedAccessTokens.size(), is(2)); - assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); - assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); - assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); + assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); - assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); - assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); - assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); - } - - @Test - public void testExportClients() throws IOException { - ClientDetailsEntity client1 = new ClientDetailsEntity(); - client1.setId(1L); - client1.setAccessTokenValiditySeconds(3600); - client1.setClientId("client1"); - client1.setClientSecret("clientsecret1"); - client1.setRedirectUris(ImmutableSet.of("http://foo.com/")); - client1.setScope(ImmutableSet.of("foo", "bar", "baz", "dolphin")); - client1.setGrantTypes(ImmutableSet.of("implicit", "authorization_code", "urn:ietf:params:oauth:grant_type:redelegate", "refresh_token")); - client1.setAllowIntrospection(true); - - ClientDetailsEntity client2 = new ClientDetailsEntity(); - client2.setId(2L); - client2.setAccessTokenValiditySeconds(3600); - client2.setClientId("client2"); - client2.setClientSecret("clientsecret2"); - client2.setRedirectUris(ImmutableSet.of("http://bar.baz.com/")); - client2.setScope(ImmutableSet.of("foo", "dolphin", "electric-wombat")); - client2.setGrantTypes(ImmutableSet.of("client_credentials", "urn:ietf:params:oauth:grant_type:redelegate")); - client2.setAllowIntrospection(false); - - Set allClients = ImmutableSet.of(client1, client2); - - Mockito.when(clientRepository.getAllClients()).thenReturn(allClients); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - - // check our client list (this test) - JsonArray clients = config.get(MITREidDataService.CLIENTS).getAsJsonArray(); - - assertThat(clients.size(), is(2)); - // check for both of our clients in turn - Set checked = new HashSet<>(); - for (JsonElement e : clients) { - assertThat(e.isJsonObject(), is(true)); - JsonObject client = e.getAsJsonObject(); - - ClientDetailsEntity compare = null; - if (client.get("clientId").getAsString().equals(client1.getClientId())) { - compare = client1; - } else if (client.get("clientId").getAsString().equals(client2.getClientId())) { - compare = client2; - } - - if (compare == null) { - fail("Could not find matching clientId: " + client.get("clientId").getAsString()); - } else { - assertThat(client.get("clientId").getAsString(), equalTo(compare.getClientId())); - assertThat(client.get("secret").getAsString(), equalTo(compare.getClientSecret())); - assertThat(client.get("accessTokenValiditySeconds").getAsInt(), equalTo(compare.getAccessTokenValiditySeconds())); - assertThat(client.get("allowIntrospection").getAsBoolean(), equalTo(compare.isAllowIntrospection())); - assertThat(jsonArrayToStringSet(client.get("redirectUris").getAsJsonArray()), equalTo(compare.getRedirectUris())); - assertThat(jsonArrayToStringSet(client.get("scope").getAsJsonArray()), equalTo(compare.getScope())); - assertThat(jsonArrayToStringSet(client.get("grantTypes").getAsJsonArray()), equalTo(compare.getGrantTypes())); - checked.add(compare); - } - } - // make sure all of our clients were found - assertThat(checked.containsAll(allClients), is(true)); + assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); } @Test @@ -832,99 +462,6 @@ public void testImportClients() throws IOException { assertThat(savedClients.get(1).isAllowIntrospection(), equalTo(client2.isAllowIntrospection())); } - @Test - public void testExportBlacklistedSites() throws IOException { - BlacklistedSite site1 = new BlacklistedSite(); - site1.setId(1L); - site1.setUri("http://foo.com"); - - BlacklistedSite site2 = new BlacklistedSite(); - site2.setId(2L); - site2.setUri("http://bar.com"); - - BlacklistedSite site3 = new BlacklistedSite(); - site3.setId(3L); - site3.setUri("http://baz.com"); - - Set allBlacklistedSites = ImmutableSet.of(site1, site2, site3); - - Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(blSiteRepository.getAll()).thenReturn(allBlacklistedSites); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - // check our scope list (this test) - JsonArray sites = config.get(MITREidDataService.BLACKLISTEDSITES).getAsJsonArray(); - - assertThat(sites.size(), is(3)); - // check for both of our sites in turn - Set checked = new HashSet<>(); - for (JsonElement e : sites) { - assertThat(e.isJsonObject(), is(true)); - JsonObject site = e.getAsJsonObject(); - - BlacklistedSite compare = null; - if (site.get("id").getAsLong() == site1.getId().longValue()) { - compare = site1; - } else if (site.get("id").getAsLong() == site2.getId().longValue()) { - compare = site2; - } else if (site.get("id").getAsLong() == site3.getId().longValue()) { - compare = site3; - } - - if (compare == null) { - fail("Could not find matching blacklisted site id: " + site.get("id").getAsString()); - } else { - assertThat(site.get("uri").getAsString(), equalTo(compare.getUri())); - checked.add(compare); - } - } - // make sure all of our clients were found - assertThat(checked.containsAll(allBlacklistedSites), is(true)); - - } - @Test public void testImportBlacklistedSites() throws IOException { BlacklistedSite site1 = new BlacklistedSite(); @@ -973,99 +510,6 @@ public void testImportBlacklistedSites() throws IOException { assertThat(savedSites.get(2).getUri(), equalTo(site3.getUri())); } - @Test - public void testExportWhitelistedSites() throws IOException { - WhitelistedSite site1 = new WhitelistedSite(); - site1.setId(1L); - site1.setClientId("foo"); - - WhitelistedSite site2 = new WhitelistedSite(); - site2.setId(2L); - site2.setClientId("bar"); - - WhitelistedSite site3 = new WhitelistedSite(); - site3.setId(3L); - site3.setClientId("baz"); - - Set allWhitelistedSites = ImmutableSet.of(site1, site2, site3); - - Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(allWhitelistedSites); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - // check our scope list (this test) - JsonArray sites = config.get(MITREidDataService.WHITELISTEDSITES).getAsJsonArray(); - - assertThat(sites.size(), is(3)); - // check for both of our sites in turn - Set checked = new HashSet<>(); - for (JsonElement e : sites) { - assertThat(e.isJsonObject(), is(true)); - JsonObject site = e.getAsJsonObject(); - - WhitelistedSite compare = null; - if (site.get("id").getAsLong() == site1.getId().longValue()) { - compare = site1; - } else if (site.get("id").getAsLong() == site2.getId().longValue()) { - compare = site2; - } else if (site.get("id").getAsLong() == site3.getId().longValue()) { - compare = site3; - } - - if (compare == null) { - fail("Could not find matching whitelisted site id: " + site.get("id").getAsString()); - } else { - assertThat(site.get("clientId").getAsString(), equalTo(compare.getClientId())); - checked.add(compare); - } - } - // make sure all of our clients were found - assertThat(checked.containsAll(allWhitelistedSites), is(true)); - - } - @Test public void testImportWhitelistedSites() throws IOException { WhitelistedSite site1 = new WhitelistedSite(); @@ -1135,135 +579,10 @@ public WhitelistedSite answer(InvocationOnMock invocation) throws Throwable { assertThat(savedSites.get(2).getClientId(), equalTo(site3.getClientId())); } - @Test - public void testExportGrants() throws IOException, ParseException { - Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+0000", Locale.ENGLISH); - Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+0000", Locale.ENGLISH); - - OAuth2AccessTokenEntity mockToken1 = mock(OAuth2AccessTokenEntity.class); - when(mockToken1.getId()).thenReturn(1L); - - ApprovedSite site1 = new ApprovedSite(); - site1.setId(1L); - site1.setClientId("foo"); - site1.setCreationDate(creationDate1); - site1.setAccessDate(accessDate1); - site1.setUserId("user1"); - site1.setAllowedScopes(ImmutableSet.of("openid", "phone")); - site1.setApprovedAccessTokens(ImmutableSet.of(mockToken1)); - - Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+0000", Locale.ENGLISH); - Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+0000", Locale.ENGLISH); - Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+0000", Locale.ENGLISH); - - ApprovedSite site2 = new ApprovedSite(); - site2.setId(2L); - site2.setClientId("bar"); - site2.setCreationDate(creationDate2); - site2.setAccessDate(accessDate2); - site2.setUserId("user2"); - site2.setAllowedScopes(ImmutableSet.of("openid", "offline_access", "email", "profile")); - site2.setTimeoutDate(timeoutDate2); - - Set allApprovedSites = ImmutableSet.of(site1, site2); - - Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(allApprovedSites); - Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - // check our scope list (this test) - JsonArray sites = config.get(MITREidDataService.GRANTS).getAsJsonArray(); - - assertThat(sites.size(), is(2)); - // check for both of our sites in turn - Set checked = new HashSet<>(); - for (JsonElement e : sites) { - assertThat(e.isJsonObject(), is(true)); - JsonObject site = e.getAsJsonObject(); - - ApprovedSite compare = null; - if (site.get("id").getAsLong() == site1.getId().longValue()) { - compare = site1; - } else if (site.get("id").getAsLong() == site2.getId().longValue()) { - compare = site2; - } - - if (compare == null) { - fail("Could not find matching whitelisted site id: " + site.get("id").getAsString()); - } else { - assertThat(site.get("clientId").getAsString(), equalTo(compare.getClientId())); - assertThat(site.get("creationDate").getAsString(), equalTo(formatter.print(compare.getCreationDate(), Locale.ENGLISH))); - assertThat(site.get("accessDate").getAsString(), equalTo(formatter.print(compare.getAccessDate(), Locale.ENGLISH))); - if(site.get("timeoutDate").isJsonNull()) { - assertNull(compare.getTimeoutDate()); - } else { - assertThat(site.get("timeoutDate").getAsString(), equalTo(formatter.print(compare.getTimeoutDate(), Locale.ENGLISH))); - } - assertThat(site.get("userId").getAsString(), equalTo(compare.getUserId())); - assertThat(jsonArrayToStringSet(site.getAsJsonArray("allowedScopes")), equalTo(compare.getAllowedScopes())); - if (site.get("approvedAccessTokens").isJsonNull() || site.getAsJsonArray("approvedAccessTokens") == null) { - assertTrue(compare.getApprovedAccessTokens() == null || compare.getApprovedAccessTokens().isEmpty()); - } else { - assertNotNull(compare.getApprovedAccessTokens()); - Set tokenIds = new HashSet<>(); - for(OAuth2AccessTokenEntity entity : compare.getApprovedAccessTokens()) { - tokenIds.add(entity.getId().toString()); - } - assertThat(jsonArrayToStringSet(site.getAsJsonArray("approvedAccessTokens")), equalTo(tokenIds)); - } - checked.add(compare); - } - } - // make sure all of our clients were found - assertThat(checked.containsAll(allApprovedSites), is(true)); - } - @Test public void testImportGrants() throws IOException, ParseException { - Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+0000", Locale.ENGLISH); - Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+0000", Locale.ENGLISH); + Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); + Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+00:00", Locale.ENGLISH); OAuth2AccessTokenEntity mockToken1 = mock(OAuth2AccessTokenEntity.class); when(mockToken1.getId()).thenReturn(1L); @@ -1275,11 +594,11 @@ public void testImportGrants() throws IOException, ParseException { site1.setAccessDate(accessDate1); site1.setUserId("user1"); site1.setAllowedScopes(ImmutableSet.of("openid", "phone")); - site1.setApprovedAccessTokens(ImmutableSet.of(mockToken1)); + when(mockToken1.getApprovedSite()).thenReturn(site1); - Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+0000", Locale.ENGLISH); - Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+0000", Locale.ENGLISH); - Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+0000", Locale.ENGLISH); + Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+00:00", Locale.ENGLISH); + Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+00:00", Locale.ENGLISH); + Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+00:00", Locale.ENGLISH); ApprovedSite site2 = new ApprovedSite(); site2.setId(2L); @@ -1300,11 +619,11 @@ public void testImportGrants() throws IOException, ParseException { "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + "\"" + MITREidDataService.GRANTS + "\": [" + - "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+0000\",\"accessDate\":\"2014-09-10T23:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+00:00\",\"accessDate\":\"2014-09-10T23:49:44.090+00:00\"," + "\"userId\":\"user1\",\"whitelistedSiteId\":null,\"allowedScopes\":[\"openid\",\"phone\"], \"whitelistedSiteId\":1," + "\"approvedAccessTokens\":[1]}," + - "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+0000\",\"accessDate\":\"2014-09-11T20:49:44.090+0000\"," - + "\"timeoutDate\":\"2014-10-01T20:49:44.090+0000\",\"userId\":\"user2\"," + "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+00:00\",\"accessDate\":\"2014-09-11T20:49:44.090+00:00\"," + + "\"timeoutDate\":\"2014-10-01T20:49:44.090+00:00\",\"userId\":\"user2\"," + "\"allowedScopes\":[\"openid\",\"offline_access\",\"email\",\"profile\"]}" + " ]" + @@ -1357,130 +676,21 @@ public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwa //2 for sites, 1 for updating access token ref on #1 verify(approvedSiteRepository, times(3)).save(capturedApprovedSites.capture()); - List savedSites = new ArrayList(fakeDb.values()); - - assertThat(savedSites.size(), is(2)); - - assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); - assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); - assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); - assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); - assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); - assertThat(savedSites.get(0).getApprovedAccessTokens().size(), equalTo(site1.getApprovedAccessTokens().size())); - - assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); - assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); - assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); - assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); - assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); - assertThat(savedSites.get(1).getApprovedAccessTokens().size(), equalTo(site2.getApprovedAccessTokens().size())); - } - - @Test - public void testExportAuthenticationHolders() throws IOException { - OAuth2Request req1 = new OAuth2Request(new HashMap(), "client1", new ArrayList(), - true, new HashSet(), new HashSet(), "http://foo.com", - new HashSet(), null); - Authentication mockAuth1 = new UsernamePasswordAuthenticationToken("user1", "pass1", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); - OAuth2Authentication auth1 = new OAuth2Authentication(req1, mockAuth1); - - AuthenticationHolderEntity holder1 = new AuthenticationHolderEntity(); - holder1.setId(1L); - holder1.setAuthentication(auth1); - - OAuth2Request req2 = new OAuth2Request(new HashMap(), "client2", new ArrayList(), - true, new HashSet(), new HashSet(), "http://bar.com", - new HashSet(), null); - OAuth2Authentication auth2 = new OAuth2Authentication(req2, null); + List savedSites = new ArrayList(fakeDb.values()); - AuthenticationHolderEntity holder2 = new AuthenticationHolderEntity(); - holder2.setId(2L); - holder2.setAuthentication(auth2); + assertThat(savedSites.size(), is(2)); - List allAuthHolders = ImmutableList.of(holder1, holder2); - - when(clientRepository.getAllClients()).thenReturn(new HashSet()); - when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - when(blSiteRepository.getAll()).thenReturn(new HashSet()); - when(authHolderRepository.getAll()).thenReturn(allAuthHolders); - when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - when(sysScopeRepository.getAll()).thenReturn(new HashSet()); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - - // check our holder list (this test) - JsonArray holders = config.get(MITREidDataService.AUTHENTICATIONHOLDERS).getAsJsonArray(); - - assertThat(holders.size(), is(2)); - // check for both of our clients in turn - Set checked = new HashSet<>(); - for (JsonElement e : holders) { - assertThat(e.isJsonObject(), is(true)); - JsonObject holder = e.getAsJsonObject(); - - AuthenticationHolderEntity compare = null; - if (holder.get("id").getAsLong() == holder1.getId()) { - compare = holder1; - } else if (holder.get("id").getAsLong() == holder2.getId()) { - compare = holder2; - } + assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); + assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); + assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); + assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); + assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); - if (compare == null) { - fail("Could not find matching authentication holder id: " + holder.get("id").getAsString()); - } else { - assertTrue(holder.get("clientId").getAsString().equals(compare.getClientId())); - assertTrue(holder.get("approved").getAsBoolean() == compare.isApproved()); - assertTrue(holder.get("redirectUri").getAsString().equals(compare.getRedirectUri())); - if (compare.getUserAuth() != null) { - assertTrue(holder.get("savedUserAuthentication").isJsonObject()); - JsonObject savedAuth = holder.get("savedUserAuthentication").getAsJsonObject(); - assertTrue(savedAuth.get("name").getAsString().equals(compare.getUserAuth().getName())); - assertTrue(savedAuth.get("authenticated").getAsBoolean() == compare.getUserAuth().isAuthenticated()); - assertTrue(savedAuth.get("sourceClass").getAsString().equals(compare.getUserAuth().getSourceClass())); - } - checked.add(compare); - } - } - // make sure all of our clients were found - assertThat(checked.containsAll(allAuthHolders), is(true)); + assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); + assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); + assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); + assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); + assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); } @Test @@ -1550,116 +760,6 @@ public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Thr assertThat(savedAuthHolders.get(1).getAuthentication().getOAuth2Request().getClientId(), equalTo(holder2.getAuthentication().getOAuth2Request().getClientId())); } - @Test - public void testExportSystemScopes() throws IOException { - SystemScope scope1 = new SystemScope(); - scope1.setId(1L); - scope1.setValue("scope1"); - scope1.setDescription("Scope 1"); - scope1.setRestricted(true); - scope1.setDefaultScope(false); - scope1.setIcon("glass"); - - SystemScope scope2 = new SystemScope(); - scope2.setId(2L); - scope2.setValue("scope2"); - scope2.setDescription("Scope 2"); - scope2.setRestricted(false); - scope2.setDefaultScope(false); - scope2.setIcon("ball"); - - SystemScope scope3 = new SystemScope(); - scope3.setId(3L); - scope3.setValue("scope3"); - scope3.setDescription("Scope 3"); - scope3.setRestricted(false); - scope3.setDefaultScope(true); - scope3.setIcon("road"); - - Set allScopes = ImmutableSet.of(scope1, scope2, scope3); - - Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); - Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); - Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); - Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); - Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); - Mockito.when(sysScopeRepository.getAll()).thenReturn(allScopes); - - // do the data export - StringWriter stringWriter = new StringWriter(); - JsonWriter writer = new JsonWriter(stringWriter); - writer.beginObject(); - dataService.exportData(writer); - writer.endObject(); - writer.close(); - - // parse the output as a JSON object for testing - JsonElement elem = new JsonParser().parse(stringWriter.toString()); - JsonObject root = elem.getAsJsonObject(); - - // make sure the root is there - assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_2), is(true)); - - JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_2).getAsJsonObject(); - - // make sure all the root elements are there - assertThat(config.has(MITREidDataService.CLIENTS), is(true)); - assertThat(config.has(MITREidDataService.GRANTS), is(true)); - assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); - assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); - assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); - assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); - assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); - - // make sure the root elements are all arrays - assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); - assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); - - - // check our scope list (this test) - JsonArray scopes = config.get(MITREidDataService.SYSTEMSCOPES).getAsJsonArray(); - - assertThat(scopes.size(), is(3)); - // check for both of our clients in turn - Set checked = new HashSet<>(); - for (JsonElement e : scopes) { - assertThat(e.isJsonObject(), is(true)); - JsonObject scope = e.getAsJsonObject(); - - SystemScope compare = null; - if (scope.get("value").getAsString().equals(scope1.getValue())) { - compare = scope1; - } else if (scope.get("value").getAsString().equals(scope2.getValue())) { - compare = scope2; - } else if (scope.get("value").getAsString().equals(scope3.getValue())) { - compare = scope3; - } - - if (compare == null) { - fail("Could not find matching scope value: " + scope.get("value").getAsString()); - } else { - assertThat(scope.get("value").getAsString(), equalTo(compare.getValue())); - assertThat(scope.get("description").getAsString(), equalTo(compare.getDescription())); - assertThat(scope.get("icon").getAsString(), equalTo(compare.getIcon())); - assertThat(scope.get("restricted").getAsBoolean(), equalTo(compare.isRestricted())); - assertThat(scope.get("defaultScope").getAsBoolean(), equalTo(compare.isDefaultScope())); - checked.add(compare); - } - } - // make sure all of our clients were found - assertThat(checked.containsAll(allScopes), is(true)); - - } - @Test public void testImportSystemScopes() throws IOException { SystemScope scope1 = new SystemScope(); @@ -1685,8 +785,6 @@ public void testImportSystemScopes() throws IOException { scope3.setRestricted(false); scope3.setDefaultScope(true); scope3.setIcon("road"); - scope3.setStructured(true); - scope3.setStructuredParamDescription("Structured Parameter"); String configJson = "{" + "\"" + MITREidDataService.CLIENTS + "\": [], " + @@ -1700,7 +798,7 @@ public void testImportSystemScopes() throws IOException { "{\"id\":1,\"description\":\"Scope 1\",\"icon\":\"glass\",\"value\":\"scope1\",\"restricted\":true,\"defaultScope\":false}," + "{\"id\":2,\"description\":\"Scope 2\",\"icon\":\"ball\",\"value\":\"scope2\",\"restricted\":false,\"defaultScope\":false}," + - "{\"id\":3,\"description\":\"Scope 3\",\"icon\":\"road\",\"value\":\"scope3\",\"restricted\":false,\"defaultScope\":true,\"structured\":true,\"structuredParameter\":\"Structured Parameter\"}" + + "{\"id\":3,\"description\":\"Scope 3\",\"icon\":\"road\",\"value\":\"scope3\",\"restricted\":false,\"defaultScope\":true}" + " ]" + "}"; @@ -1720,30 +818,24 @@ public void testImportSystemScopes() throws IOException { assertThat(savedScopes.get(0).getIcon(), equalTo(scope1.getIcon())); assertThat(savedScopes.get(0).isDefaultScope(), equalTo(scope1.isDefaultScope())); assertThat(savedScopes.get(0).isRestricted(), equalTo(scope1.isRestricted())); - assertThat(savedScopes.get(0).isStructured(), equalTo(scope1.isStructured())); - assertThat(savedScopes.get(0).getStructuredParamDescription(), equalTo(scope1.getStructuredParamDescription())); assertThat(savedScopes.get(1).getValue(), equalTo(scope2.getValue())); assertThat(savedScopes.get(1).getDescription(), equalTo(scope2.getDescription())); assertThat(savedScopes.get(1).getIcon(), equalTo(scope2.getIcon())); assertThat(savedScopes.get(1).isDefaultScope(), equalTo(scope2.isDefaultScope())); assertThat(savedScopes.get(1).isRestricted(), equalTo(scope2.isRestricted())); - assertThat(savedScopes.get(1).isStructured(), equalTo(scope2.isStructured())); - assertThat(savedScopes.get(1).getStructuredParamDescription(), equalTo(scope2.getStructuredParamDescription())); assertThat(savedScopes.get(2).getValue(), equalTo(scope3.getValue())); assertThat(savedScopes.get(2).getDescription(), equalTo(scope3.getDescription())); assertThat(savedScopes.get(2).getIcon(), equalTo(scope3.getIcon())); assertThat(savedScopes.get(2).isDefaultScope(), equalTo(scope3.isDefaultScope())); assertThat(savedScopes.get(2).isRestricted(), equalTo(scope3.isRestricted())); - assertThat(savedScopes.get(2).isStructured(), equalTo(scope3.isStructured())); - assertThat(savedScopes.get(2).getStructuredParamDescription(), equalTo(scope3.getStructuredParamDescription())); } @Test public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException, ParseException { - String expiration1 = "2014-09-10T22:49:44.090+0000"; + String expiration1 = "2014-09-10T22:49:44.090+00:00"; Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); @@ -1766,7 +858,7 @@ public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); token1.setAuthenticationHolder(holder1); - String expiration2 = "2015-01-07T18:31:50.079+0000"; + String expiration2 = "2015-01-07T18:31:50.079+00:00"; Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); @@ -1805,9 +897,9 @@ public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException " ]," + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + - "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+0000\"," + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + - "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+0000\"," + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + " ]" + diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_3.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_3.java new file mode 100644 index 0000000000..19a12c2350 --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestMITREidDataService_1_3.java @@ -0,0 +1,1858 @@ +/******************************************************************************* + * 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.impl; + +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.isA; +import static org.mockito.Matchers.isNull; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mitre.oauth2.model.AuthenticationHolderEntity; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; +import org.mitre.oauth2.model.PKCEAlgorithm; +import org.mitre.oauth2.model.SystemScope; +import org.mitre.oauth2.repository.AuthenticationHolderRepository; +import org.mitre.oauth2.repository.OAuth2ClientRepository; +import org.mitre.oauth2.repository.OAuth2TokenRepository; +import org.mitre.oauth2.repository.SystemScopeRepository; +import org.mitre.openid.connect.model.ApprovedSite; +import org.mitre.openid.connect.model.BlacklistedSite; +import org.mitre.openid.connect.model.WhitelistedSite; +import org.mitre.openid.connect.repository.ApprovedSiteRepository; +import org.mitre.openid.connect.repository.BlacklistedSiteRepository; +import org.mitre.openid.connect.repository.WhitelistedSiteRepository; +import org.mitre.openid.connect.service.MITREidDataService; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.format.annotation.DateTimeFormat.ISO; +import org.springframework.format.datetime.DateFormatter; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import com.nimbusds.jwt.JWTParser; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings(value = {"rawtypes", "unchecked"}) +public class TestMITREidDataService_1_3 { + + private static Logger logger = LoggerFactory.getLogger(TestMITREidDataService_1_3.class); + + @Mock + private OAuth2ClientRepository clientRepository; + @Mock + private ApprovedSiteRepository approvedSiteRepository; + @Mock + private WhitelistedSiteRepository wlSiteRepository; + @Mock + private BlacklistedSiteRepository blSiteRepository; + @Mock + private AuthenticationHolderRepository authHolderRepository; + @Mock + private OAuth2TokenRepository tokenRepository; + @Mock + private SystemScopeRepository sysScopeRepository; + + @Captor + private ArgumentCaptor capturedRefreshTokens; + @Captor + private ArgumentCaptor capturedAccessTokens; + @Captor + private ArgumentCaptor capturedClients; + @Captor + private ArgumentCaptor capturedBlacklistedSites; + @Captor + private ArgumentCaptor capturedWhitelistedSites; + @Captor + private ArgumentCaptor capturedApprovedSites; + @Captor + private ArgumentCaptor capturedAuthHolders; + @Captor + private ArgumentCaptor capturedScope; + + @InjectMocks + private MITREidDataService_1_3 dataService; + private DateFormatter formatter; + + @Before + public void prepare() { + formatter = new DateFormatter(); + formatter.setIso(ISO.DATE_TIME); + + Mockito.reset(clientRepository, approvedSiteRepository, authHolderRepository, tokenRepository, sysScopeRepository, wlSiteRepository, blSiteRepository); + } + + @Test + public void testExportRefreshTokens() throws IOException, ParseException { + String expiration1 = "2014-09-10T22:49:44.090+00:00"; + Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); + + ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); + when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); + + AuthenticationHolderEntity mockedAuthHolder1 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder1.getId()).thenReturn(1L); + + OAuth2RefreshTokenEntity token1 = new OAuth2RefreshTokenEntity(); + token1.setId(1L); + token1.setClient(mockedClient1); + token1.setExpiration(expirationDate1); + token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); + token1.setAuthenticationHolder(mockedAuthHolder1); + + String expiration2 = "2015-01-07T18:31:50.079+00:00"; + Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); + + ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); + when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); + + AuthenticationHolderEntity mockedAuthHolder2 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder2.getId()).thenReturn(2L); + + OAuth2RefreshTokenEntity token2 = new OAuth2RefreshTokenEntity(); + token2.setId(2L); + token2.setClient(mockedClient2); + token2.setExpiration(expirationDate2); + token2.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.")); + token2.setAuthenticationHolder(mockedAuthHolder2); + + Set allRefreshTokens = ImmutableSet.of(token1, token2); + + Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(allRefreshTokens); + Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + + // check our refresh token list (this test) + JsonArray refreshTokens = config.get(MITREidDataService.REFRESHTOKENS).getAsJsonArray(); + + assertThat(refreshTokens.size(), is(2)); + // check for both of our refresh tokens in turn + Set checked = new HashSet<>(); + for (JsonElement e : refreshTokens) { + assertThat(e.isJsonObject(), is(true)); + JsonObject token = e.getAsJsonObject(); + + OAuth2RefreshTokenEntity compare = null; + if (token.get("id").getAsLong() == token1.getId()) { + compare = token1; + } else if (token.get("id").getAsLong() == token2.getId()) { + compare = token2; + } + + if (compare == null) { + fail("Could not find matching id: " + token.get("id").getAsString()); + } else { + assertThat(token.get("id").getAsLong(), equalTo(compare.getId())); + assertThat(token.get("clientId").getAsString(), equalTo(compare.getClient().getClientId())); + assertThat(token.get("expiration").getAsString(), equalTo(formatter.print(compare.getExpiration(), Locale.ENGLISH))); + assertThat(token.get("value").getAsString(), equalTo(compare.getValue())); + assertThat(token.get("authenticationHolderId").getAsLong(), equalTo(compare.getAuthenticationHolder().getId())); + checked.add(compare); + } + } + // make sure all of our refresh tokens were found + assertThat(checked.containsAll(allRefreshTokens), is(true)); + } + + private class refreshTokenIdComparator implements Comparator { + @Override + public int compare(OAuth2RefreshTokenEntity entity1, OAuth2RefreshTokenEntity entity2) { + return entity1.getId().compareTo(entity2.getId()); + } + } + + + @Test + public void testImportRefreshTokens() throws IOException, ParseException { + String expiration1 = "2014-09-10T22:49:44.090+00:00"; + Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); + + ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); + when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); + + AuthenticationHolderEntity mockedAuthHolder1 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder1.getId()).thenReturn(1L); + + OAuth2RefreshTokenEntity token1 = new OAuth2RefreshTokenEntity(); + token1.setId(1L); + token1.setClient(mockedClient1); + token1.setExpiration(expirationDate1); + token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); + token1.setAuthenticationHolder(mockedAuthHolder1); + + String expiration2 = "2015-01-07T18:31:50.079+00:00"; + Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); + + ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); + when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); + + AuthenticationHolderEntity mockedAuthHolder2 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder2.getId()).thenReturn(2L); + + OAuth2RefreshTokenEntity token2 = new OAuth2RefreshTokenEntity(); + token2.setId(2L); + token2.setClient(mockedClient2); + token2.setExpiration(expirationDate2); + token2.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.")); + token2.setAuthenticationHolder(mockedAuthHolder2); + + String configJson = "{" + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + + + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + + + " ]" + + "}"; + + logger.debug(configJson); + JsonReader reader = new JsonReader(new StringReader(configJson)); + + final Map fakeDb = new HashMap<>(); + when(tokenRepository.saveRefreshToken(isA(OAuth2RefreshTokenEntity.class))).thenAnswer(new Answer() { + Long id = 332L; + @Override + public OAuth2RefreshTokenEntity answer(InvocationOnMock invocation) throws Throwable { + OAuth2RefreshTokenEntity _token = (OAuth2RefreshTokenEntity) invocation.getArguments()[0]; + if(_token.getId() == null) { + _token.setId(id++); + } + fakeDb.put(_token.getId(), _token); + return _token; + } + }); + when(tokenRepository.getRefreshTokenById(anyLong())).thenAnswer(new Answer() { + @Override + public OAuth2RefreshTokenEntity answer(InvocationOnMock invocation) throws Throwable { + Long _id = (Long) invocation.getArguments()[0]; + return fakeDb.get(_id); + } + }); + when(clientRepository.getClientByClientId(anyString())).thenAnswer(new Answer() { + @Override + public ClientDetailsEntity answer(InvocationOnMock invocation) throws Throwable { + String _clientId = (String) invocation.getArguments()[0]; + ClientDetailsEntity _client = mock(ClientDetailsEntity.class); + when(_client.getClientId()).thenReturn(_clientId); + return _client; + } + }); + when(authHolderRepository.getById(isNull(Long.class))).thenAnswer(new Answer() { + Long id = 131L; + @Override + public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Throwable { + AuthenticationHolderEntity _auth = mock(AuthenticationHolderEntity.class); + when(_auth.getId()).thenReturn(id); + id++; + return _auth; + } + }); + dataService.importData(reader); + //2 times for token, 2 times to update client, 2 times to update authHolder + verify(tokenRepository, times(6)).saveRefreshToken(capturedRefreshTokens.capture()); + + List savedRefreshTokens = new ArrayList(fakeDb.values()); //capturedRefreshTokens.getAllValues(); + Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); + + assertThat(savedRefreshTokens.size(), is(2)); + + assertThat(savedRefreshTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedRefreshTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedRefreshTokens.get(0).getValue(), equalTo(token1.getValue())); + + assertThat(savedRefreshTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedRefreshTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedRefreshTokens.get(1).getValue(), equalTo(token2.getValue())); + } + + @Test + public void testExportAccessTokens() throws IOException, ParseException { + String expiration1 = "2014-09-10T22:49:44.090+00:00"; + Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); + + ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); + when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); + + AuthenticationHolderEntity mockedAuthHolder1 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder1.getId()).thenReturn(1L); + + OAuth2AccessTokenEntity token1 = new OAuth2AccessTokenEntity(); + token1.setId(1L); + token1.setClient(mockedClient1); + token1.setExpiration(expirationDate1); + token1.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0")); + token1.setAuthenticationHolder(mockedAuthHolder1); + token1.setScope(ImmutableSet.of("id-token")); + token1.setTokenType("Bearer"); + + String expiration2 = "2015-01-07T18:31:50.079+00:00"; + Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); + + ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); + when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); + + AuthenticationHolderEntity mockedAuthHolder2 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder2.getId()).thenReturn(2L); + + OAuth2RefreshTokenEntity mockRefreshToken2 = mock(OAuth2RefreshTokenEntity.class); + when(mockRefreshToken2.getId()).thenReturn(1L); + + OAuth2AccessTokenEntity token2 = new OAuth2AccessTokenEntity(); + token2.setId(2L); + token2.setClient(mockedClient2); + token2.setExpiration(expirationDate2); + token2.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ")); + token2.setAuthenticationHolder(mockedAuthHolder2); + token2.setRefreshToken(mockRefreshToken2); + token2.setScope(ImmutableSet.of("openid", "offline_access", "email", "profile")); + token2.setTokenType("Bearer"); + + Set allAccessTokens = ImmutableSet.of(token1, token2); + + Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(allAccessTokens); + Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + + // check our access token list (this test) + JsonArray accessTokens = config.get(MITREidDataService.ACCESSTOKENS).getAsJsonArray(); + + assertThat(accessTokens.size(), is(2)); + // check for both of our access tokens in turn + Set checked = new HashSet<>(); + for (JsonElement e : accessTokens) { + assertTrue(e.isJsonObject()); + JsonObject token = e.getAsJsonObject(); + + OAuth2AccessTokenEntity compare = null; + if (token.get("id").getAsLong() == token1.getId().longValue()) { + compare = token1; + } else if (token.get("id").getAsLong() == token2.getId().longValue()) { + compare = token2; + } + + if (compare == null) { + fail("Could not find matching id: " + token.get("id").getAsString()); + } else { + assertThat(token.get("id").getAsLong(), equalTo(compare.getId())); + assertThat(token.get("clientId").getAsString(), equalTo(compare.getClient().getClientId())); + assertThat(token.get("expiration").getAsString(), equalTo(formatter.print(compare.getExpiration(), Locale.ENGLISH))); + assertThat(token.get("value").getAsString(), equalTo(compare.getValue())); + assertThat(token.get("type").getAsString(), equalTo(compare.getTokenType())); + assertThat(token.get("authenticationHolderId").getAsLong(), equalTo(compare.getAuthenticationHolder().getId())); + assertTrue(token.get("scope").isJsonArray()); + assertThat(jsonArrayToStringSet(token.getAsJsonArray("scope")), equalTo(compare.getScope())); + if(token.get("refreshTokenId").isJsonNull()) { + assertNull(compare.getRefreshToken()); + } else { + assertThat(token.get("refreshTokenId").getAsLong(), equalTo(compare.getRefreshToken().getId())); + } + checked.add(compare); + } + } + // make sure all of our access tokens were found + assertThat(checked.containsAll(allAccessTokens), is(true)); + } + + private class accessTokenIdComparator implements Comparator { + @Override + public int compare(OAuth2AccessTokenEntity entity1, OAuth2AccessTokenEntity entity2) { + return entity1.getId().compareTo(entity2.getId()); + } + } + + @Test + public void testImportAccessTokens() throws IOException, ParseException { + String expiration1 = "2014-09-10T22:49:44.090+00:00"; + Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); + + ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); + when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); + + AuthenticationHolderEntity mockedAuthHolder1 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder1.getId()).thenReturn(1L); + + OAuth2AccessTokenEntity token1 = new OAuth2AccessTokenEntity(); + token1.setId(1L); + token1.setClient(mockedClient1); + token1.setExpiration(expirationDate1); + token1.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0")); + token1.setAuthenticationHolder(mockedAuthHolder1); + token1.setScope(ImmutableSet.of("id-token")); + token1.setTokenType("Bearer"); + + String expiration2 = "2015-01-07T18:31:50.079+00:00"; + Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); + + ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); + when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); + + AuthenticationHolderEntity mockedAuthHolder2 = mock(AuthenticationHolderEntity.class); + when(mockedAuthHolder2.getId()).thenReturn(2L); + + OAuth2RefreshTokenEntity mockRefreshToken2 = mock(OAuth2RefreshTokenEntity.class); + when(mockRefreshToken2.getId()).thenReturn(1L); + + OAuth2AccessTokenEntity token2 = new OAuth2AccessTokenEntity(); + token2.setId(2L); + token2.setClient(mockedClient2); + token2.setExpiration(expirationDate2); + token2.setJwt(JWTParser.parse("eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ")); + token2.setAuthenticationHolder(mockedAuthHolder2); + token2.setRefreshToken(mockRefreshToken2); + token2.setScope(ImmutableSet.of("openid", "offline_access", "email", "profile")); + token2.setTokenType("Bearer"); + + String configJson = "{" + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [" + + + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + + "\"refreshTokenId\":null,\"idTokenId\":null,\"scope\":[\"id-token\"],\"type\":\"Bearer\"," + + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3ODk5NjgsInN1YiI6IjkwMzQyLkFTREZKV0ZBIiwiYXRfaGFzaCI6InptTmt1QmNRSmNYQktNaVpFODZqY0EiLCJhdWQiOlsiY2xpZW50Il0sImlzcyI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgwXC9vcGVuaWQtY29ubmVjdC1zZXJ2ZXItd2ViYXBwXC8iLCJpYXQiOjE0MTI3ODkzNjh9.xkEJ9IMXpH7qybWXomfq9WOOlpGYnrvGPgey9UQ4GLzbQx7JC0XgJK83PmrmBZosvFPCmota7FzI_BtwoZLgAZfFiH6w3WIlxuogoH-TxmYbxEpTHoTsszZppkq9mNgOlArV4jrR9y3TPo4MovsH71dDhS_ck-CvAlJunHlqhs0\"}," + + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + + "\"refreshTokenId\":1,\"idTokenId\":1,\"scope\":[\"openid\",\"offline_access\",\"email\",\"profile\"],\"type\":\"Bearer\"," + + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE0MTI3OTI5NjgsImF1ZCI6WyJjbGllbnQiXSwiaXNzIjoiaHR0cDpcL1wvbG9jYWxob3N0OjgwODBcL29wZW5pZC1jb25uZWN0LXNlcnZlci13ZWJhcHBcLyIsImp0aSI6IjBmZGE5ZmRiLTYyYzItNGIzZS05OTdiLWU0M2VhMDUwMzNiOSIsImlhdCI6MTQxMjc4OTM2OH0.xgaVpRLYE5MzbgXfE0tZt823tjAm6Oh3_kdR1P2I9jRLR6gnTlBQFlYi3Y_0pWNnZSerbAE8Tn6SJHZ9k-curVG0-ByKichV7CNvgsE5X_2wpEaUzejvKf8eZ-BammRY-ie6yxSkAarcUGMvGGOLbkFcz5CtrBpZhfd75J49BIQ\"}" + + + " ]" + + "}"; + + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + final Map fakeDb = new HashMap<>(); + when(tokenRepository.saveAccessToken(isA(OAuth2AccessTokenEntity.class))).thenAnswer(new Answer() { + Long id = 324L; + @Override + public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwable { + OAuth2AccessTokenEntity _token = (OAuth2AccessTokenEntity) invocation.getArguments()[0]; + if(_token.getId() == null) { + _token.setId(id++); + } + fakeDb.put(_token.getId(), _token); + return _token; + } + }); + when(tokenRepository.getAccessTokenById(anyLong())).thenAnswer(new Answer() { + @Override + public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwable { + Long _id = (Long) invocation.getArguments()[0]; + return fakeDb.get(_id); + } + }); + when(clientRepository.getClientByClientId(anyString())).thenAnswer(new Answer() { + @Override + public ClientDetailsEntity answer(InvocationOnMock invocation) throws Throwable { + String _clientId = (String) invocation.getArguments()[0]; + ClientDetailsEntity _client = mock(ClientDetailsEntity.class); + when(_client.getClientId()).thenReturn(_clientId); + return _client; + } + }); + when(authHolderRepository.getById(isNull(Long.class))).thenAnswer(new Answer() { + Long id = 133L; + @Override + public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Throwable { + AuthenticationHolderEntity _auth = mock(AuthenticationHolderEntity.class); + when(_auth.getId()).thenReturn(id); + id++; + return _auth; + } + }); + dataService.importData(reader); + //2 times for token, 2 times to update client, 2 times to update authHolder, 1 times to update refresh token + verify(tokenRepository, times(7)).saveAccessToken(capturedAccessTokens.capture()); + + List savedAccessTokens = new ArrayList(fakeDb.values()); //capturedAccessTokens.getAllValues(); + Collections.sort(savedAccessTokens, new accessTokenIdComparator()); + + assertThat(savedAccessTokens.size(), is(2)); + + assertThat(savedAccessTokens.get(0).getClient().getClientId(), equalTo(token1.getClient().getClientId())); + assertThat(savedAccessTokens.get(0).getExpiration(), equalTo(token1.getExpiration())); + assertThat(savedAccessTokens.get(0).getValue(), equalTo(token1.getValue())); + + assertThat(savedAccessTokens.get(1).getClient().getClientId(), equalTo(token2.getClient().getClientId())); + assertThat(savedAccessTokens.get(1).getExpiration(), equalTo(token2.getExpiration())); + assertThat(savedAccessTokens.get(1).getValue(), equalTo(token2.getValue())); + } + + @Test + public void testExportClients() throws IOException { + ClientDetailsEntity client1 = new ClientDetailsEntity(); + client1.setId(1L); + client1.setAccessTokenValiditySeconds(3600); + client1.setClientId("client1"); + client1.setClientSecret("clientsecret1"); + client1.setRedirectUris(ImmutableSet.of("http://foo.com/")); + client1.setScope(ImmutableSet.of("foo", "bar", "baz", "dolphin")); + client1.setGrantTypes(ImmutableSet.of("implicit", "authorization_code", "urn:ietf:params:oauth:grant_type:redelegate", "refresh_token")); + client1.setAllowIntrospection(true); + + ClientDetailsEntity client2 = new ClientDetailsEntity(); + client2.setId(2L); + client2.setAccessTokenValiditySeconds(3600); + client2.setClientId("client2"); + client2.setClientSecret("clientsecret2"); + client2.setRedirectUris(ImmutableSet.of("http://bar.baz.com/")); + client2.setScope(ImmutableSet.of("foo", "dolphin", "electric-wombat")); + client2.setGrantTypes(ImmutableSet.of("client_credentials", "urn:ietf:params:oauth:grant_type:redelegate")); + client2.setAllowIntrospection(false); + client2.setCodeChallengeMethod(PKCEAlgorithm.S256); + + Set allClients = ImmutableSet.of(client1, client2); + + Mockito.when(clientRepository.getAllClients()).thenReturn(allClients); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + + // check our client list (this test) + JsonArray clients = config.get(MITREidDataService.CLIENTS).getAsJsonArray(); + + assertThat(clients.size(), is(2)); + // check for both of our clients in turn + Set checked = new HashSet<>(); + for (JsonElement e : clients) { + assertThat(e.isJsonObject(), is(true)); + JsonObject client = e.getAsJsonObject(); + + ClientDetailsEntity compare = null; + if (client.get("clientId").getAsString().equals(client1.getClientId())) { + compare = client1; + } else if (client.get("clientId").getAsString().equals(client2.getClientId())) { + compare = client2; + } + + if (compare == null) { + fail("Could not find matching clientId: " + client.get("clientId").getAsString()); + } else { + assertThat(client.get("clientId").getAsString(), equalTo(compare.getClientId())); + assertThat(client.get("secret").getAsString(), equalTo(compare.getClientSecret())); + assertThat(client.get("accessTokenValiditySeconds").getAsInt(), equalTo(compare.getAccessTokenValiditySeconds())); + assertThat(client.get("allowIntrospection").getAsBoolean(), equalTo(compare.isAllowIntrospection())); + assertThat(jsonArrayToStringSet(client.get("redirectUris").getAsJsonArray()), equalTo(compare.getRedirectUris())); + assertThat(jsonArrayToStringSet(client.get("scope").getAsJsonArray()), equalTo(compare.getScope())); + assertThat(jsonArrayToStringSet(client.get("grantTypes").getAsJsonArray()), equalTo(compare.getGrantTypes())); + assertThat((client.has("codeChallengeMethod") && !client.get("codeChallengeMethod").isJsonNull()) ? PKCEAlgorithm.parse(client.get("codeChallengeMethod").getAsString()) : null, equalTo(compare.getCodeChallengeMethod())); + checked.add(compare); + } + } + // make sure all of our clients were found + assertThat(checked.containsAll(allClients), is(true)); + } + + @Test + public void testImportClients() throws IOException { + ClientDetailsEntity client1 = new ClientDetailsEntity(); + client1.setId(1L); + client1.setAccessTokenValiditySeconds(3600); + client1.setClientId("client1"); + client1.setClientSecret("clientsecret1"); + client1.setRedirectUris(ImmutableSet.of("http://foo.com/")); + client1.setScope(ImmutableSet.of("foo", "bar", "baz", "dolphin")); + client1.setGrantTypes(ImmutableSet.of("implicit", "authorization_code", "urn:ietf:params:oauth:grant_type:redelegate", "refresh_token")); + client1.setAllowIntrospection(true); + + ClientDetailsEntity client2 = new ClientDetailsEntity(); + client2.setId(2L); + client2.setAccessTokenValiditySeconds(3600); + client2.setClientId("client2"); + client2.setClientSecret("clientsecret2"); + client2.setRedirectUris(ImmutableSet.of("http://bar.baz.com/")); + client2.setScope(ImmutableSet.of("foo", "dolphin", "electric-wombat")); + client2.setGrantTypes(ImmutableSet.of("client_credentials", "urn:ietf:params:oauth:grant_type:redelegate")); + client2.setAllowIntrospection(false); + + String configJson = "{" + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.CLIENTS + "\": [" + + + "{\"id\":1,\"accessTokenValiditySeconds\":3600,\"clientId\":\"client1\",\"secret\":\"clientsecret1\"," + + "\"redirectUris\":[\"http://foo.com/\"]," + + "\"scope\":[\"foo\",\"bar\",\"baz\",\"dolphin\"]," + + "\"grantTypes\":[\"implicit\",\"authorization_code\",\"urn:ietf:params:oauth:grant_type:redelegate\",\"refresh_token\"]," + + "\"allowIntrospection\":true}," + + "{\"id\":2,\"accessTokenValiditySeconds\":3600,\"clientId\":\"client2\",\"secret\":\"clientsecret2\"," + + "\"redirectUris\":[\"http://bar.baz.com/\"]," + + "\"scope\":[\"foo\",\"dolphin\",\"electric-wombat\"]," + + "\"grantTypes\":[\"client_credentials\",\"urn:ietf:params:oauth:grant_type:redelegate\"]," + + "\"allowIntrospection\":false}" + + + " ]" + + "}"; + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + dataService.importData(reader); + verify(clientRepository, times(2)).saveClient(capturedClients.capture()); + + List savedClients = capturedClients.getAllValues(); + + assertThat(savedClients.size(), is(2)); + + assertThat(savedClients.get(0).getAccessTokenValiditySeconds(), equalTo(client1.getAccessTokenValiditySeconds())); + assertThat(savedClients.get(0).getClientId(), equalTo(client1.getClientId())); + assertThat(savedClients.get(0).getClientSecret(), equalTo(client1.getClientSecret())); + assertThat(savedClients.get(0).getRedirectUris(), equalTo(client1.getRedirectUris())); + assertThat(savedClients.get(0).getScope(), equalTo(client1.getScope())); + assertThat(savedClients.get(0).getGrantTypes(), equalTo(client1.getGrantTypes())); + assertThat(savedClients.get(0).isAllowIntrospection(), equalTo(client1.isAllowIntrospection())); + + assertThat(savedClients.get(1).getAccessTokenValiditySeconds(), equalTo(client2.getAccessTokenValiditySeconds())); + assertThat(savedClients.get(1).getClientId(), equalTo(client2.getClientId())); + assertThat(savedClients.get(1).getClientSecret(), equalTo(client2.getClientSecret())); + assertThat(savedClients.get(1).getRedirectUris(), equalTo(client2.getRedirectUris())); + assertThat(savedClients.get(1).getScope(), equalTo(client2.getScope())); + assertThat(savedClients.get(1).getGrantTypes(), equalTo(client2.getGrantTypes())); + assertThat(savedClients.get(1).isAllowIntrospection(), equalTo(client2.isAllowIntrospection())); + } + + @Test + public void testExportBlacklistedSites() throws IOException { + BlacklistedSite site1 = new BlacklistedSite(); + site1.setId(1L); + site1.setUri("http://foo.com"); + + BlacklistedSite site2 = new BlacklistedSite(); + site2.setId(2L); + site2.setUri("http://bar.com"); + + BlacklistedSite site3 = new BlacklistedSite(); + site3.setId(3L); + site3.setUri("http://baz.com"); + + Set allBlacklistedSites = ImmutableSet.of(site1, site2, site3); + + Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(blSiteRepository.getAll()).thenReturn(allBlacklistedSites); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + // check our scope list (this test) + JsonArray sites = config.get(MITREidDataService.BLACKLISTEDSITES).getAsJsonArray(); + + assertThat(sites.size(), is(3)); + // check for both of our sites in turn + Set checked = new HashSet<>(); + for (JsonElement e : sites) { + assertThat(e.isJsonObject(), is(true)); + JsonObject site = e.getAsJsonObject(); + + BlacklistedSite compare = null; + if (site.get("id").getAsLong() == site1.getId().longValue()) { + compare = site1; + } else if (site.get("id").getAsLong() == site2.getId().longValue()) { + compare = site2; + } else if (site.get("id").getAsLong() == site3.getId().longValue()) { + compare = site3; + } + + if (compare == null) { + fail("Could not find matching blacklisted site id: " + site.get("id").getAsString()); + } else { + assertThat(site.get("uri").getAsString(), equalTo(compare.getUri())); + checked.add(compare); + } + } + // make sure all of our clients were found + assertThat(checked.containsAll(allBlacklistedSites), is(true)); + + } + + @Test + public void testImportBlacklistedSites() throws IOException { + BlacklistedSite site1 = new BlacklistedSite(); + site1.setId(1L); + site1.setUri("http://foo.com"); + + BlacklistedSite site2 = new BlacklistedSite(); + site2.setId(2L); + site2.setUri("http://bar.com"); + + BlacklistedSite site3 = new BlacklistedSite(); + site3.setId(3L); + site3.setUri("http://baz.com"); + + String configJson = "{" + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [" + + + "{\"id\":1,\"uri\":\"http://foo.com\"}," + + "{\"id\":2,\"uri\":\"http://bar.com\"}," + + "{\"id\":3,\"uri\":\"http://baz.com\"}" + + + " ]" + + "}"; + + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + dataService.importData(reader); + verify(blSiteRepository, times(3)).save(capturedBlacklistedSites.capture()); + + List savedSites = capturedBlacklistedSites.getAllValues(); + + assertThat(savedSites.size(), is(3)); + + assertThat(savedSites.get(0).getUri(), equalTo(site1.getUri())); + assertThat(savedSites.get(1).getUri(), equalTo(site2.getUri())); + assertThat(savedSites.get(2).getUri(), equalTo(site3.getUri())); + } + + @Test + public void testExportWhitelistedSites() throws IOException { + WhitelistedSite site1 = new WhitelistedSite(); + site1.setId(1L); + site1.setClientId("foo"); + + WhitelistedSite site2 = new WhitelistedSite(); + site2.setId(2L); + site2.setClientId("bar"); + + WhitelistedSite site3 = new WhitelistedSite(); + site3.setId(3L); + site3.setClientId("baz"); + + Set allWhitelistedSites = ImmutableSet.of(site1, site2, site3); + + Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(allWhitelistedSites); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + // check our scope list (this test) + JsonArray sites = config.get(MITREidDataService.WHITELISTEDSITES).getAsJsonArray(); + + assertThat(sites.size(), is(3)); + // check for both of our sites in turn + Set checked = new HashSet<>(); + for (JsonElement e : sites) { + assertThat(e.isJsonObject(), is(true)); + JsonObject site = e.getAsJsonObject(); + + WhitelistedSite compare = null; + if (site.get("id").getAsLong() == site1.getId().longValue()) { + compare = site1; + } else if (site.get("id").getAsLong() == site2.getId().longValue()) { + compare = site2; + } else if (site.get("id").getAsLong() == site3.getId().longValue()) { + compare = site3; + } + + if (compare == null) { + fail("Could not find matching whitelisted site id: " + site.get("id").getAsString()); + } else { + assertThat(site.get("clientId").getAsString(), equalTo(compare.getClientId())); + checked.add(compare); + } + } + // make sure all of our clients were found + assertThat(checked.containsAll(allWhitelistedSites), is(true)); + + } + + @Test + public void testImportWhitelistedSites() throws IOException { + WhitelistedSite site1 = new WhitelistedSite(); + site1.setId(1L); + site1.setClientId("foo"); + + WhitelistedSite site2 = new WhitelistedSite(); + site2.setId(2L); + site2.setClientId("bar"); + + WhitelistedSite site3 = new WhitelistedSite(); + site3.setId(3L); + site3.setClientId("baz"); + //site3.setAllowedScopes(null); + + String configJson = "{" + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [" + + + "{\"id\":1,\"clientId\":\"foo\"}," + + "{\"id\":2,\"clientId\":\"bar\"}," + + "{\"id\":3,\"clientId\":\"baz\"}" + + + " ]" + + "}"; + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + final Map fakeDb = new HashMap<>(); + when(wlSiteRepository.save(isA(WhitelistedSite.class))).thenAnswer(new Answer() { + Long id = 333L; + @Override + public WhitelistedSite answer(InvocationOnMock invocation) throws Throwable { + WhitelistedSite _site = (WhitelistedSite) invocation.getArguments()[0]; + if(_site.getId() == null) { + _site.setId(id++); + } + fakeDb.put(_site.getId(), _site); + return _site; + } + }); + when(wlSiteRepository.getById(anyLong())).thenAnswer(new Answer() { + @Override + public WhitelistedSite answer(InvocationOnMock invocation) throws Throwable { + Long _id = (Long) invocation.getArguments()[0]; + return fakeDb.get(_id); + } + }); + + dataService.importData(reader); + verify(wlSiteRepository, times(3)).save(capturedWhitelistedSites.capture()); + + List savedSites = capturedWhitelistedSites.getAllValues(); + + assertThat(savedSites.size(), is(3)); + + assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); + assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); + assertThat(savedSites.get(2).getClientId(), equalTo(site3.getClientId())); + } + + @Test + public void testExportGrants() throws IOException, ParseException { + Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); + Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+00:00", Locale.ENGLISH); + + OAuth2AccessTokenEntity mockToken1 = mock(OAuth2AccessTokenEntity.class); + when(mockToken1.getId()).thenReturn(1L); + + ApprovedSite site1 = new ApprovedSite(); + site1.setId(1L); + site1.setClientId("foo"); + site1.setCreationDate(creationDate1); + site1.setAccessDate(accessDate1); + site1.setUserId("user1"); + site1.setAllowedScopes(ImmutableSet.of("openid", "phone")); + when(mockToken1.getApprovedSite()).thenReturn(site1); + + Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+00:00", Locale.ENGLISH); + Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+00:00", Locale.ENGLISH); + Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+00:00", Locale.ENGLISH); + + ApprovedSite site2 = new ApprovedSite(); + site2.setId(2L); + site2.setClientId("bar"); + site2.setCreationDate(creationDate2); + site2.setAccessDate(accessDate2); + site2.setUserId("user2"); + site2.setAllowedScopes(ImmutableSet.of("openid", "offline_access", "email", "profile")); + site2.setTimeoutDate(timeoutDate2); + + Set allApprovedSites = ImmutableSet.of(site1, site2); + + Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(allApprovedSites); + Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + Mockito.when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + // check our scope list (this test) + JsonArray sites = config.get(MITREidDataService.GRANTS).getAsJsonArray(); + + assertThat(sites.size(), is(2)); + // check for both of our sites in turn + Set checked = new HashSet<>(); + for (JsonElement e : sites) { + assertThat(e.isJsonObject(), is(true)); + JsonObject site = e.getAsJsonObject(); + + ApprovedSite compare = null; + if (site.get("id").getAsLong() == site1.getId().longValue()) { + compare = site1; + } else if (site.get("id").getAsLong() == site2.getId().longValue()) { + compare = site2; + } + + if (compare == null) { + fail("Could not find matching whitelisted site id: " + site.get("id").getAsString()); + } else { + assertThat(site.get("clientId").getAsString(), equalTo(compare.getClientId())); + assertThat(site.get("creationDate").getAsString(), equalTo(formatter.print(compare.getCreationDate(), Locale.ENGLISH))); + assertThat(site.get("accessDate").getAsString(), equalTo(formatter.print(compare.getAccessDate(), Locale.ENGLISH))); + if(site.get("timeoutDate").isJsonNull()) { + assertNull(compare.getTimeoutDate()); + } else { + assertThat(site.get("timeoutDate").getAsString(), equalTo(formatter.print(compare.getTimeoutDate(), Locale.ENGLISH))); + } + assertThat(site.get("userId").getAsString(), equalTo(compare.getUserId())); + assertThat(jsonArrayToStringSet(site.getAsJsonArray("allowedScopes")), equalTo(compare.getAllowedScopes())); + checked.add(compare); + } + } + // make sure all of our clients were found + assertThat(checked.containsAll(allApprovedSites), is(true)); + } + + @Test + public void testImportGrants() throws IOException, ParseException { + Date creationDate1 = formatter.parse("2014-09-10T22:49:44.090+00:00", Locale.ENGLISH); + Date accessDate1 = formatter.parse("2014-09-10T23:49:44.090+00:00", Locale.ENGLISH); + + OAuth2AccessTokenEntity mockToken1 = mock(OAuth2AccessTokenEntity.class); + when(mockToken1.getId()).thenReturn(1L); + + ApprovedSite site1 = new ApprovedSite(); + site1.setId(1L); + site1.setClientId("foo"); + site1.setCreationDate(creationDate1); + site1.setAccessDate(accessDate1); + site1.setUserId("user1"); + site1.setAllowedScopes(ImmutableSet.of("openid", "phone")); + when(mockToken1.getApprovedSite()).thenReturn(site1); + + Date creationDate2 = formatter.parse("2014-09-11T18:49:44.090+00:00", Locale.ENGLISH); + Date accessDate2 = formatter.parse("2014-09-11T20:49:44.090+00:00", Locale.ENGLISH); + Date timeoutDate2 = formatter.parse("2014-10-01T20:49:44.090+00:00", Locale.ENGLISH); + + ApprovedSite site2 = new ApprovedSite(); + site2.setId(2L); + site2.setClientId("bar"); + site2.setCreationDate(creationDate2); + site2.setAccessDate(accessDate2); + site2.setUserId("user2"); + site2.setAllowedScopes(ImmutableSet.of("openid", "offline_access", "email", "profile")); + site2.setTimeoutDate(timeoutDate2); + + String configJson = "{" + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [" + + + "{\"id\":1,\"clientId\":\"foo\",\"creationDate\":\"2014-09-10T22:49:44.090+00:00\",\"accessDate\":\"2014-09-10T23:49:44.090+00:00\"," + + "\"userId\":\"user1\",\"whitelistedSiteId\":null,\"allowedScopes\":[\"openid\",\"phone\"], \"whitelistedSiteId\":1," + + "\"approvedAccessTokens\":[1]}," + + "{\"id\":2,\"clientId\":\"bar\",\"creationDate\":\"2014-09-11T18:49:44.090+00:00\",\"accessDate\":\"2014-09-11T20:49:44.090+00:00\"," + + "\"timeoutDate\":\"2014-10-01T20:49:44.090+00:00\",\"userId\":\"user2\"," + + "\"allowedScopes\":[\"openid\",\"offline_access\",\"email\",\"profile\"]}" + + + " ]" + + "}"; + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + final Map fakeDb = new HashMap<>(); + when(approvedSiteRepository.save(isA(ApprovedSite.class))).thenAnswer(new Answer() { + Long id = 364L; + @Override + public ApprovedSite answer(InvocationOnMock invocation) throws Throwable { + ApprovedSite _site = (ApprovedSite) invocation.getArguments()[0]; + if(_site.getId() == null) { + _site.setId(id++); + } + fakeDb.put(_site.getId(), _site); + return _site; + } + }); + when(approvedSiteRepository.getById(anyLong())).thenAnswer(new Answer() { + @Override + public ApprovedSite answer(InvocationOnMock invocation) throws Throwable { + Long _id = (Long) invocation.getArguments()[0]; + return fakeDb.get(_id); + } + }); + when(wlSiteRepository.getById(isNull(Long.class))).thenAnswer(new Answer() { + Long id = 432L; + @Override + public WhitelistedSite answer(InvocationOnMock invocation) throws Throwable { + WhitelistedSite _site = mock(WhitelistedSite.class); + when(_site.getId()).thenReturn(id++); + return _site; + } + }); + when(tokenRepository.getAccessTokenById(isNull(Long.class))).thenAnswer(new Answer() { + Long id = 245L; + @Override + public OAuth2AccessTokenEntity answer(InvocationOnMock invocation) throws Throwable { + OAuth2AccessTokenEntity _token = mock(OAuth2AccessTokenEntity.class); + when(_token.getId()).thenReturn(id++); + return _token; + } + }); + + dataService.importData(reader); + //2 for sites, 1 for updating access token ref on #1 + verify(approvedSiteRepository, times(3)).save(capturedApprovedSites.capture()); + + List savedSites = new ArrayList(fakeDb.values()); + + assertThat(savedSites.size(), is(2)); + + assertThat(savedSites.get(0).getClientId(), equalTo(site1.getClientId())); + assertThat(savedSites.get(0).getAccessDate(), equalTo(site1.getAccessDate())); + assertThat(savedSites.get(0).getCreationDate(), equalTo(site1.getCreationDate())); + assertThat(savedSites.get(0).getAllowedScopes(), equalTo(site1.getAllowedScopes())); + assertThat(savedSites.get(0).getTimeoutDate(), equalTo(site1.getTimeoutDate())); + + assertThat(savedSites.get(1).getClientId(), equalTo(site2.getClientId())); + assertThat(savedSites.get(1).getAccessDate(), equalTo(site2.getAccessDate())); + assertThat(savedSites.get(1).getCreationDate(), equalTo(site2.getCreationDate())); + assertThat(savedSites.get(1).getAllowedScopes(), equalTo(site2.getAllowedScopes())); + assertThat(savedSites.get(1).getTimeoutDate(), equalTo(site2.getTimeoutDate())); + } + + @Test + public void testExportAuthenticationHolders() throws IOException { + OAuth2Request req1 = new OAuth2Request(new HashMap(), "client1", new ArrayList(), + true, new HashSet(), new HashSet(), "http://foo.com", + new HashSet(), null); + Authentication mockAuth1 = new UsernamePasswordAuthenticationToken("user1", "pass1", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER")); + OAuth2Authentication auth1 = new OAuth2Authentication(req1, mockAuth1); + + AuthenticationHolderEntity holder1 = new AuthenticationHolderEntity(); + holder1.setId(1L); + holder1.setAuthentication(auth1); + + OAuth2Request req2 = new OAuth2Request(new HashMap(), "client2", new ArrayList(), + true, new HashSet(), new HashSet(), "http://bar.com", + new HashSet(), null); + OAuth2Authentication auth2 = new OAuth2Authentication(req2, null); + + AuthenticationHolderEntity holder2 = new AuthenticationHolderEntity(); + holder2.setId(2L); + holder2.setAuthentication(auth2); + + List allAuthHolders = ImmutableList.of(holder1, holder2); + + when(clientRepository.getAllClients()).thenReturn(new HashSet()); + when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + when(blSiteRepository.getAll()).thenReturn(new HashSet()); + when(authHolderRepository.getAll()).thenReturn(allAuthHolders); + when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + when(sysScopeRepository.getAll()).thenReturn(new HashSet()); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + + // check our holder list (this test) + JsonArray holders = config.get(MITREidDataService.AUTHENTICATIONHOLDERS).getAsJsonArray(); + + assertThat(holders.size(), is(2)); + // check for both of our clients in turn + Set checked = new HashSet<>(); + for (JsonElement e : holders) { + assertThat(e.isJsonObject(), is(true)); + JsonObject holder = e.getAsJsonObject(); + + AuthenticationHolderEntity compare = null; + if (holder.get("id").getAsLong() == holder1.getId()) { + compare = holder1; + } else if (holder.get("id").getAsLong() == holder2.getId()) { + compare = holder2; + } + + if (compare == null) { + fail("Could not find matching authentication holder id: " + holder.get("id").getAsString()); + } else { + assertTrue(holder.get("clientId").getAsString().equals(compare.getClientId())); + assertTrue(holder.get("approved").getAsBoolean() == compare.isApproved()); + assertTrue(holder.get("redirectUri").getAsString().equals(compare.getRedirectUri())); + if (compare.getUserAuth() != null) { + assertTrue(holder.get("savedUserAuthentication").isJsonObject()); + JsonObject savedAuth = holder.get("savedUserAuthentication").getAsJsonObject(); + assertTrue(savedAuth.get("name").getAsString().equals(compare.getUserAuth().getName())); + assertTrue(savedAuth.get("authenticated").getAsBoolean() == compare.getUserAuth().isAuthenticated()); + assertTrue(savedAuth.get("sourceClass").getAsString().equals(compare.getUserAuth().getSourceClass())); + } + checked.add(compare); + } + } + // make sure all of our clients were found + assertThat(checked.containsAll(allAuthHolders), is(true)); + } + + @Test + public void testImportAuthenticationHolders() throws IOException { + OAuth2Request req1 = new OAuth2Request(new HashMap(), "client1", new ArrayList(), + true, new HashSet(), new HashSet(), "http://foo.com", + new HashSet(), null); + Authentication mockAuth1 = mock(Authentication.class, withSettings().serializable()); + OAuth2Authentication auth1 = new OAuth2Authentication(req1, mockAuth1); + + AuthenticationHolderEntity holder1 = new AuthenticationHolderEntity(); + holder1.setId(1L); + holder1.setAuthentication(auth1); + + OAuth2Request req2 = new OAuth2Request(new HashMap(), "client2", new ArrayList(), + true, new HashSet(), new HashSet(), "http://bar.com", + new HashSet(), null); + Authentication mockAuth2 = mock(Authentication.class, withSettings().serializable()); + OAuth2Authentication auth2 = new OAuth2Authentication(req2, mockAuth2); + + AuthenticationHolderEntity holder2 = new AuthenticationHolderEntity(); + holder2.setId(2L); + holder2.setAuthentication(auth2); + + String configJson = "{" + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [" + + + "{\"id\":1,\"clientId\":\"client1\",\"redirectUri\":\"http://foo.com\"," + + "\"savedUserAuthentication\":null}," + + "{\"id\":2,\"clientId\":\"client2\",\"redirectUri\":\"http://bar.com\"," + + "\"savedUserAuthentication\":null}" + + " ]" + + "}"; + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + final Map fakeDb = new HashMap<>(); + when(authHolderRepository.save(isA(AuthenticationHolderEntity.class))).thenAnswer(new Answer() { + Long id = 243L; + @Override + public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Throwable { + AuthenticationHolderEntity _site = (AuthenticationHolderEntity) invocation.getArguments()[0]; + if(_site.getId() == null) { + _site.setId(id++); + } + fakeDb.put(_site.getId(), _site); + return _site; + } + }); + + dataService.importData(reader); + verify(authHolderRepository, times(2)).save(capturedAuthHolders.capture()); + + List savedAuthHolders = capturedAuthHolders.getAllValues(); + + assertThat(savedAuthHolders.size(), is(2)); + assertThat(savedAuthHolders.get(0).getAuthentication().getOAuth2Request().getClientId(), equalTo(holder1.getAuthentication().getOAuth2Request().getClientId())); + assertThat(savedAuthHolders.get(1).getAuthentication().getOAuth2Request().getClientId(), equalTo(holder2.getAuthentication().getOAuth2Request().getClientId())); + } + + @Test + public void testExportSystemScopes() throws IOException { + SystemScope scope1 = new SystemScope(); + scope1.setId(1L); + scope1.setValue("scope1"); + scope1.setDescription("Scope 1"); + scope1.setRestricted(true); + scope1.setDefaultScope(false); + scope1.setIcon("glass"); + + SystemScope scope2 = new SystemScope(); + scope2.setId(2L); + scope2.setValue("scope2"); + scope2.setDescription("Scope 2"); + scope2.setRestricted(false); + scope2.setDefaultScope(false); + scope2.setIcon("ball"); + + SystemScope scope3 = new SystemScope(); + scope3.setId(3L); + scope3.setValue("scope3"); + scope3.setDescription("Scope 3"); + scope3.setRestricted(false); + scope3.setDefaultScope(true); + scope3.setIcon("road"); + + Set allScopes = ImmutableSet.of(scope1, scope2, scope3); + + Mockito.when(clientRepository.getAllClients()).thenReturn(new HashSet()); + Mockito.when(approvedSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(wlSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(blSiteRepository.getAll()).thenReturn(new HashSet()); + Mockito.when(authHolderRepository.getAll()).thenReturn(new ArrayList()); + Mockito.when(tokenRepository.getAllAccessTokens()).thenReturn(new HashSet()); + Mockito.when(tokenRepository.getAllRefreshTokens()).thenReturn(new HashSet()); + Mockito.when(sysScopeRepository.getAll()).thenReturn(allScopes); + + // do the data export + StringWriter stringWriter = new StringWriter(); + JsonWriter writer = new JsonWriter(stringWriter); + writer.beginObject(); + dataService.exportData(writer); + writer.endObject(); + writer.close(); + + // parse the output as a JSON object for testing + JsonElement elem = new JsonParser().parse(stringWriter.toString()); + JsonObject root = elem.getAsJsonObject(); + + // make sure the root is there + assertThat(root.has(MITREidDataService.MITREID_CONNECT_1_3), is(true)); + + JsonObject config = root.get(MITREidDataService.MITREID_CONNECT_1_3).getAsJsonObject(); + + // make sure all the root elements are there + assertThat(config.has(MITREidDataService.CLIENTS), is(true)); + assertThat(config.has(MITREidDataService.GRANTS), is(true)); + assertThat(config.has(MITREidDataService.WHITELISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.BLACKLISTEDSITES), is(true)); + assertThat(config.has(MITREidDataService.REFRESHTOKENS), is(true)); + assertThat(config.has(MITREidDataService.ACCESSTOKENS), is(true)); + assertThat(config.has(MITREidDataService.SYSTEMSCOPES), is(true)); + assertThat(config.has(MITREidDataService.AUTHENTICATIONHOLDERS), is(true)); + + // make sure the root elements are all arrays + assertThat(config.get(MITREidDataService.CLIENTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.GRANTS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.WHITELISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.BLACKLISTEDSITES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.REFRESHTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.ACCESSTOKENS).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.SYSTEMSCOPES).isJsonArray(), is(true)); + assertThat(config.get(MITREidDataService.AUTHENTICATIONHOLDERS).isJsonArray(), is(true)); + + + // check our scope list (this test) + JsonArray scopes = config.get(MITREidDataService.SYSTEMSCOPES).getAsJsonArray(); + + assertThat(scopes.size(), is(3)); + // check for both of our clients in turn + Set checked = new HashSet<>(); + for (JsonElement e : scopes) { + assertThat(e.isJsonObject(), is(true)); + JsonObject scope = e.getAsJsonObject(); + + SystemScope compare = null; + if (scope.get("value").getAsString().equals(scope1.getValue())) { + compare = scope1; + } else if (scope.get("value").getAsString().equals(scope2.getValue())) { + compare = scope2; + } else if (scope.get("value").getAsString().equals(scope3.getValue())) { + compare = scope3; + } + + if (compare == null) { + fail("Could not find matching scope value: " + scope.get("value").getAsString()); + } else { + assertThat(scope.get("value").getAsString(), equalTo(compare.getValue())); + assertThat(scope.get("description").getAsString(), equalTo(compare.getDescription())); + assertThat(scope.get("icon").getAsString(), equalTo(compare.getIcon())); + assertThat(scope.get("restricted").getAsBoolean(), equalTo(compare.isRestricted())); + assertThat(scope.get("defaultScope").getAsBoolean(), equalTo(compare.isDefaultScope())); + checked.add(compare); + } + } + // make sure all of our clients were found + assertThat(checked.containsAll(allScopes), is(true)); + + } + + @Test + public void testImportSystemScopes() throws IOException { + SystemScope scope1 = new SystemScope(); + scope1.setId(1L); + scope1.setValue("scope1"); + scope1.setDescription("Scope 1"); + scope1.setRestricted(true); + scope1.setDefaultScope(false); + scope1.setIcon("glass"); + + SystemScope scope2 = new SystemScope(); + scope2.setId(2L); + scope2.setValue("scope2"); + scope2.setDescription("Scope 2"); + scope2.setRestricted(false); + scope2.setDefaultScope(false); + scope2.setIcon("ball"); + + SystemScope scope3 = new SystemScope(); + scope3.setId(3L); + scope3.setValue("scope3"); + scope3.setDescription("Scope 3"); + scope3.setRestricted(false); + scope3.setDefaultScope(true); + scope3.setIcon("road"); + + String configJson = "{" + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [], " + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [" + + + "{\"id\":1,\"description\":\"Scope 1\",\"icon\":\"glass\",\"value\":\"scope1\",\"restricted\":true,\"defaultScope\":false}," + + "{\"id\":2,\"description\":\"Scope 2\",\"icon\":\"ball\",\"value\":\"scope2\",\"restricted\":false,\"defaultScope\":false}," + + "{\"id\":3,\"description\":\"Scope 3\",\"icon\":\"road\",\"value\":\"scope3\",\"restricted\":false,\"defaultScope\":true}" + + + " ]" + + "}"; + + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + + dataService.importData(reader); + verify(sysScopeRepository, times(3)).save(capturedScope.capture()); + + List savedScopes = capturedScope.getAllValues(); + + assertThat(savedScopes.size(), is(3)); + assertThat(savedScopes.get(0).getValue(), equalTo(scope1.getValue())); + assertThat(savedScopes.get(0).getDescription(), equalTo(scope1.getDescription())); + assertThat(savedScopes.get(0).getIcon(), equalTo(scope1.getIcon())); + assertThat(savedScopes.get(0).isDefaultScope(), equalTo(scope1.isDefaultScope())); + assertThat(savedScopes.get(0).isRestricted(), equalTo(scope1.isRestricted())); + + assertThat(savedScopes.get(1).getValue(), equalTo(scope2.getValue())); + assertThat(savedScopes.get(1).getDescription(), equalTo(scope2.getDescription())); + assertThat(savedScopes.get(1).getIcon(), equalTo(scope2.getIcon())); + assertThat(savedScopes.get(1).isDefaultScope(), equalTo(scope2.isDefaultScope())); + assertThat(savedScopes.get(1).isRestricted(), equalTo(scope2.isRestricted())); + + assertThat(savedScopes.get(2).getValue(), equalTo(scope3.getValue())); + assertThat(savedScopes.get(2).getDescription(), equalTo(scope3.getDescription())); + assertThat(savedScopes.get(2).getIcon(), equalTo(scope3.getIcon())); + assertThat(savedScopes.get(2).isDefaultScope(), equalTo(scope3.isDefaultScope())); + assertThat(savedScopes.get(2).isRestricted(), equalTo(scope3.isRestricted())); + + } + + @Test + public void testFixRefreshTokenAuthHolderReferencesOnImport() throws IOException, ParseException { + String expiration1 = "2014-09-10T22:49:44.090+00:00"; + Date expirationDate1 = formatter.parse(expiration1, Locale.ENGLISH); + + ClientDetailsEntity mockedClient1 = mock(ClientDetailsEntity.class); + when(mockedClient1.getClientId()).thenReturn("mocked_client_1"); + + OAuth2Request req1 = new OAuth2Request(new HashMap(), "client1", new ArrayList(), + true, new HashSet(), new HashSet(), "http://foo.com", + new HashSet(), null); + Authentication mockAuth1 = mock(Authentication.class, withSettings().serializable()); + OAuth2Authentication auth1 = new OAuth2Authentication(req1, mockAuth1); + + AuthenticationHolderEntity holder1 = new AuthenticationHolderEntity(); + holder1.setId(1L); + holder1.setAuthentication(auth1); + + OAuth2RefreshTokenEntity token1 = new OAuth2RefreshTokenEntity(); + token1.setId(1L); + token1.setClient(mockedClient1); + token1.setExpiration(expirationDate1); + token1.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.")); + token1.setAuthenticationHolder(holder1); + + String expiration2 = "2015-01-07T18:31:50.079+00:00"; + Date expirationDate2 = formatter.parse(expiration2, Locale.ENGLISH); + + ClientDetailsEntity mockedClient2 = mock(ClientDetailsEntity.class); + when(mockedClient2.getClientId()).thenReturn("mocked_client_2"); + + OAuth2Request req2 = new OAuth2Request(new HashMap(), "client2", new ArrayList(), + true, new HashSet(), new HashSet(), "http://bar.com", + new HashSet(), null); + Authentication mockAuth2 = mock(Authentication.class, withSettings().serializable()); + OAuth2Authentication auth2 = new OAuth2Authentication(req2, mockAuth2); + + AuthenticationHolderEntity holder2 = new AuthenticationHolderEntity(); + holder2.setId(2L); + holder2.setAuthentication(auth2); + + OAuth2RefreshTokenEntity token2 = new OAuth2RefreshTokenEntity(); + token2.setId(2L); + token2.setClient(mockedClient2); + token2.setExpiration(expirationDate2); + token2.setJwt(JWTParser.parse("eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.")); + token2.setAuthenticationHolder(holder2); + + String configJson = "{" + + "\"" + MITREidDataService.SYSTEMSCOPES + "\": [], " + + "\"" + MITREidDataService.ACCESSTOKENS + "\": [], " + + "\"" + MITREidDataService.CLIENTS + "\": [], " + + "\"" + MITREidDataService.GRANTS + "\": [], " + + "\"" + MITREidDataService.WHITELISTEDSITES + "\": [], " + + "\"" + MITREidDataService.BLACKLISTEDSITES + "\": [], " + + "\"" + MITREidDataService.AUTHENTICATIONHOLDERS + "\": [" + + + "{\"id\":1,\"authentication\":{\"authorizationRequest\":{\"clientId\":\"client1\",\"redirectUri\":\"http://foo.com\"}," + + "\"userAuthentication\":null}}," + + "{\"id\":2,\"authentication\":{\"authorizationRequest\":{\"clientId\":\"client2\",\"redirectUri\":\"http://bar.com\"}," + + "\"userAuthentication\":null}}" + + " ]," + + "\"" + MITREidDataService.REFRESHTOKENS + "\": [" + + + "{\"id\":1,\"clientId\":\"mocked_client_1\",\"expiration\":\"2014-09-10T22:49:44.090+00:00\"," + + "\"authenticationHolderId\":1,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJmOTg4OWQyOS0xMTk1LTQ4ODEtODgwZC1lZjVlYzAwY2Y4NDIifQ.\"}," + + "{\"id\":2,\"clientId\":\"mocked_client_2\",\"expiration\":\"2015-01-07T18:31:50.079+00:00\"," + + "\"authenticationHolderId\":2,\"value\":\"eyJhbGciOiJub25lIn0.eyJqdGkiOiJlYmEyYjc3My0xNjAzLTRmNDAtOWQ3MS1hMGIxZDg1OWE2MDAifQ.\"}" + + + " ]" + + "}"; + logger.debug(configJson); + + JsonReader reader = new JsonReader(new StringReader(configJson)); + final Map fakeRefreshTokenTable = new HashMap<>(); + final Map fakeAuthHolderTable = new HashMap<>(); + when(tokenRepository.saveRefreshToken(isA(OAuth2RefreshTokenEntity.class))).thenAnswer(new Answer() { + Long id = 343L; + @Override + public OAuth2RefreshTokenEntity answer(InvocationOnMock invocation) throws Throwable { + OAuth2RefreshTokenEntity _token = (OAuth2RefreshTokenEntity) invocation.getArguments()[0]; + if(_token.getId() == null) { + _token.setId(id++); + } + fakeRefreshTokenTable.put(_token.getId(), _token); + return _token; + } + }); + when(tokenRepository.getRefreshTokenById(anyLong())).thenAnswer(new Answer() { + @Override + public OAuth2RefreshTokenEntity answer(InvocationOnMock invocation) throws Throwable { + Long _id = (Long) invocation.getArguments()[0]; + return fakeRefreshTokenTable.get(_id); + } + }); + when(clientRepository.getClientByClientId(anyString())).thenAnswer(new Answer() { + @Override + public ClientDetailsEntity answer(InvocationOnMock invocation) throws Throwable { + String _clientId = (String) invocation.getArguments()[0]; + ClientDetailsEntity _client = mock(ClientDetailsEntity.class); + when(_client.getClientId()).thenReturn(_clientId); + return _client; + } + }); + when(authHolderRepository.save(isA(AuthenticationHolderEntity.class))).thenAnswer(new Answer() { + Long id = 356L; + @Override + public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Throwable { + AuthenticationHolderEntity _holder = (AuthenticationHolderEntity) invocation.getArguments()[0]; + if(_holder.getId() == null) { + _holder.setId(id++); + } + fakeAuthHolderTable.put(_holder.getId(), _holder); + return _holder; + } + }); + when(authHolderRepository.getById(anyLong())).thenAnswer(new Answer() { + @Override + public AuthenticationHolderEntity answer(InvocationOnMock invocation) throws Throwable { + Long _id = (Long) invocation.getArguments()[0]; + return fakeAuthHolderTable.get(_id); + } + }); + dataService.importData(reader); + + List savedRefreshTokens = new ArrayList(fakeRefreshTokenTable.values()); //capturedRefreshTokens.getAllValues(); + Collections.sort(savedRefreshTokens, new refreshTokenIdComparator()); + + assertThat(savedRefreshTokens.get(0).getAuthenticationHolder().getId(), equalTo(356L)); + assertThat(savedRefreshTokens.get(1).getAuthenticationHolder().getId(), equalTo(357L)); + } + + private Set jsonArrayToStringSet(JsonArray a) { + Set s = new HashSet<>(); + for (JsonElement jsonElement : a) { + s.add(jsonElement.getAsString()); + } + return s; + } + +} diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java index 46fa42c9a2..30e9f5f514 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/service/impl/TestUUIDPairwiseIdentiferService.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -15,7 +16,7 @@ * limitations under the License. *******************************************************************************/ /** - * + * */ package org.mitre.openid.connect.service.impl; diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/token/TestConnectTokenEnhancer.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/token/TestConnectTokenEnhancer.java new file mode 100644 index 0000000000..7e1acbfd99 --- /dev/null +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/token/TestConnectTokenEnhancer.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * 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.token; + +import java.text.ParseException; + +import org.mitre.jwt.signer.service.JWTSigningAndValidationService; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.service.ClientDetailsEntityService; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.model.UserInfo; +import org.mitre.openid.connect.service.OIDCTokenService; +import org.mitre.openid.connect.service.UserInfoService; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; + +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jwt.JWT; +import com.nimbusds.jwt.JWTClaimsSet.Builder; + +@RunWith(MockitoJUnitRunner.class) +public class TestConnectTokenEnhancer { + + private static final String CLIENT_ID = "client"; + private static final String KEY_ID = "key"; + + private ConfigurationPropertiesBean configBean = new ConfigurationPropertiesBean(); + + @Mock + private JWTSigningAndValidationService jwtService; + + @Mock + private ClientDetailsEntityService clientService; + + @Mock + private UserInfoService userInfoService; + + @Mock + private OIDCTokenService connectTokenService; + + @Mock + private OAuth2Authentication authentication; + + private OAuth2Request request = new OAuth2Request(CLIENT_ID) { }; + + @InjectMocks + private ConnectTokenEnhancer enhancer = new ConnectTokenEnhancer(); + + @Before + public void prepare() { + configBean.setIssuer("https://auth.example.org/"); + enhancer.setConfigBean(configBean); + + ClientDetailsEntity client = new ClientDetailsEntity(); + client.setClientId(CLIENT_ID); + Mockito.when(clientService.loadClientByClientId(Mockito.anyString())).thenReturn(client); + Mockito.when(authentication.getOAuth2Request()).thenReturn(request); + Mockito.when(jwtService.getDefaultSigningAlgorithm()).thenReturn(JWSAlgorithm.RS256); + Mockito.when(jwtService.getDefaultSignerKeyId()).thenReturn(KEY_ID); + } + + @Test + public void invokesCustomClaimsHook() throws ParseException { + configure(enhancer = new ConnectTokenEnhancer() { + @Override + protected void addCustomAccessTokenClaims(Builder builder, OAuth2AccessTokenEntity token, + OAuth2Authentication authentication) { + builder.claim("test", "foo"); + } + }); + + OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); + + OAuth2AccessTokenEntity enhanced = (OAuth2AccessTokenEntity) enhancer.enhance(token, authentication); + Assert.assertEquals("foo", enhanced.getJwt().getJWTClaimsSet().getClaim("test")); + } + + private void configure(ConnectTokenEnhancer e) { + e.setConfigBean(configBean); + e.setJwtService(jwtService); + e.setClientService(clientService); + } +} diff --git a/openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java b/openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java index ff8aaf51c0..b8ccbfd4ca 100644 --- a/openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java +++ b/openid-connect-server/src/test/java/org/mitre/openid/connect/util/TestIdTokenHashUtils.java @@ -1,6 +1,7 @@ /******************************************************************************* - * Copyright 2015 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. @@ -34,7 +35,7 @@ import static org.junit.Assert.assertEquals; /** - * + * * @author wkim * */ diff --git a/openid-connect-server/src/test/resources/resources/js/locale/en/messages.json b/openid-connect-server/src/test/resources/resources/js/locale/en/messages.json new file mode 100644 index 0000000000..6c5b3d3cbc --- /dev/null +++ b/openid-connect-server/src/test/resources/resources/js/locale/en/messages.json @@ -0,0 +1,3 @@ +{ + "testAttribute": "testValue" +} diff --git a/pom.xml b/pom.xml index 7b37e7519d..3c223dae63 100644 --- a/pom.xml +++ b/pom.xml @@ -1,25 +1,26 @@ + 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 org.mitre openid-connect-parent - 1.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT MITREid Connect pom @@ -69,10 +70,8 @@ - 1.7 - 4.1.7.RELEASE - 3.2.7.RELEASE - 1.7.7 + 11 + 1.7.25 A reference implementation of OpenID Connect (http://openid.net/connect/), OAuth 2.0, and UMA built on top of Java, Spring, and Spring Security. The project contains a fully functioning server, client, and utility library. https://github.com/mitreid-connect @@ -82,54 +81,120 @@ org.apache.maven.plugins maven-jar-plugin - 2.4 + 3.0.2 org.apache.maven.plugins maven-war-plugin - 2.2 + 3.0.0 org.apache.maven.plugins - maven-archiver - 2.5 + maven-javadoc-plugin + 2.10.4 + + + org.jacoco + jacoco-maven-plugin + 0.8.7 org.apache.maven.plugins - maven-javadoc-plugin - 2.9 + maven-checkstyle-plugin + 2.10 org.apache.maven.plugins maven-deploy-plugin - 2.7 + 2.8.2 org.apache.maven.plugins maven-source-plugin - 2.2 + 3.0.1 org.apache.maven.plugins maven-compiler-plugin - 2.5.1 + 3.6.1 org.eclipse.jetty jetty-maven-plugin - 9.1.1.v20140108 + 9.4.3.v20170317 true org.apache.maven.plugins maven-enforcer-plugin - 1.3 + 1.4.1 org.appfuse.plugins warpath-maven-plugin 3.5.0 + + org.apache.maven.plugins + maven-site-plugin + 3.6 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 2.9 + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.appfuse.plugins + + + warpath-maven-plugin + + + [3.5.0,) + + + add-classes + + + + + + + + + + + + ro.isdc.wro4j + wro4j-maven-plugin + 1.10.0 + + + compile + + run + + + + + + ro.isdc.wro4j + wro4j-extensions + 1.10.0 + + + @@ -144,6 +209,7 @@ MITREid Connect v. ${project.version} MITREid Connect v. ${project.version} ${basedir}/src/main/javadoc/overview.html + -Xdoclint:none @@ -195,6 +261,93 @@ + + org.jacoco + jacoco-maven-plugin + + + + prepare-agent + + + + report + test + + report + + + + + + org.apache.maven.plugins + maven-site-plugin + + + + org.apache.maven.plugins + maven-project-info-reports-plugin + + false + false + + + index + cim + dependencies + dependency-convergence + + dependency-management + help + issue-tracking + license + mailing-list + modules + plugin-management + plugins + project-team + scm + summary + + + + org.apache.maven.plugins + maven-javadoc-plugin + + true + true + true + true + MITREid Connect ${project.name} v. ${project.version} + MITREid Connect ${project.name} v. ${project.version} + ${basedir}/src/main/javadoc/overview.html + -Xdoclint:none + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + checkstyle.xml + + + + org.apache.maven.plugins + maven-surefire-plugin + + junit:junit + + **/*_Roo_* + + + + + org.jacoco + jacoco-maven-plugin + + + + @@ -206,148 +359,43 @@ https://travis-ci.org/mitreid-connect/OpenID-Connect-Java-Spring-Server - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9 - - true - true - true - true - MITREid Connect v. ${project.version} - MITREid Connect v. ${project.version} - ${basedir}/src/main/javadoc/overview.html - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.10 - - checkstyle.xml - - - - org.apache.maven.plugins - maven-surefire-plugin - - junit:junit - - **/*_Roo_* - - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.5.2 - - - html - xml - - - - - - - - org.springframework - spring-core - ${org.springframework-version} - - - org.springframework - spring-webmvc - ${org.springframework-version} + spring-framework-bom + 5.3.9 + pom + import + + com.fasterxml.jackson.core jackson-databind - 2.3.4 + 2.9.8 com.fasterxml.jackson.core jackson-annotations - 2.3.4 - - - org.springframework - spring-tx - ${org.springframework-version} + 2.9.8 - - org.springframework - spring-orm - ${org.springframework-version} - - - - commons-logging - commons-logging - - - - + org.springframework.security - spring-security-core - ${spring.security.version} - - - org.springframework - * - - - - - org.springframework.security - spring-security-config - ${spring.security.version} - - - org.springframework - * - - - - - org.springframework.security - spring-security-taglibs - ${spring.security.version} - - - org.springframework - * - - - - - org.springframework.security - spring-security-web - ${spring.security.version} - - - org.springframework - * - - + spring-security-bom + 5.5.2 + pom + import org.springframework.security.oauth - 2.0.3.RELEASE spring-security-oauth2 + 2.1.0.RELEASE - + javax.servlet @@ -371,27 +419,37 @@ mysql mysql-connector-java - 5.1.34 + 5.1.42 org.hsqldb hsqldb - 2.2.9 + 2.3.4 org.postgresql postgresql - 9.4-1201-jdbc4 + 42.0.0.jre7 + + + com.oracle + ojdbc6 + 11.1.0.7.0 org.eclipse.persistence org.eclipse.persistence.jpa - 2.5.1 + 2.7.4 org.eclipse.persistence javax.persistence - 2.1.0 + 2.2.1 + + + com.zaxxer + HikariCP + 2.6.1 @@ -411,7 +469,6 @@ org.slf4j jcl-over-slf4j ${org.slf4j-version} - runtime org.slf4j @@ -422,7 +479,7 @@ log4j log4j - 1.2.15 + 1.2.17 javax.mail @@ -448,19 +505,13 @@ junit junit - 4.7 + 4.12 test org.easymock easymock - 2.0 - test - - - org.springframework - spring-test - ${org.springframework-version} + 3.4 test @@ -469,7 +520,6 @@ 1.9.5 test - org.mitre @@ -514,28 +564,75 @@ com.google.guava guava - 18.0 + 27.0-jre com.google.code.gson gson - 2.3.1 + 2.8.0 org.apache.httpcomponents httpclient - 4.3.6 + 4.5.3 + + + commons-logging + commons-logging + + com.nimbusds nimbus-jose-jwt - 3.9 + 5.4 - - com.zaxxer - HikariCP-java6 - 2.3.8 + org.bouncycastle + bcprov-jdk15on + [1.52,) + + + org.eclipse.persistence + org.eclipse.persistence.core + 2.7.4 + + + org.apache.commons + commons-io + 1.3.2 + + + ro.isdc.wro4j + wro4j-extensions + 1.10.0 + + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + jakarta.xml.bind + jakarta.xml.bind-api + 3.0.0 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + javax.activation + activation + 1.1 + + + org.glassfish.jaxb + jaxb-runtime + 2.3.0-b170127.1453 @@ -553,6 +650,12 @@ org.springframework spring-test + + + commons-logging + commons-logging + + org.mockito @@ -562,6 +665,10 @@ org.slf4j slf4j-jdk14 + + org.slf4j + jcl-over-slf4j + javax.servlet servlet-api @@ -572,27 +679,4 @@ - - - - - 1.8 - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9 - - -Xdoclint:none - - - - - - - - diff --git a/uma-server-webapp/pom.xml b/uma-server-webapp/pom.xml index 3b7aa23e27..a1db8a2739 100644 --- a/uma-server-webapp/pom.xml +++ b/uma-server-webapp/pom.xml @@ -1,26 +1,25 @@ + 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. + --> 4.0.0 org.mitre openid-connect-parent - 1.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT .. uma-server-webapp @@ -53,50 +52,27 @@ org.apache.maven.plugins maven-war-plugin + uma-server-webapp org.mitre openid-connect-server-webapp + false + + + + org.eclipse.jetty + jetty-maven-plugin + + ${project.build.directory}/uma-server-webapp.war + + /uma-server-webapp + - - - - - org.eclipse.m2e - lifecycle-mapping - 1.0.0 - - - - - - - org.appfuse.plugins - - - warpath-maven-plugin - - - [3.5.0,) - - - add-classes - - - - - - - - - - - - diff --git a/uma-server-webapp/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java b/uma-server-webapp/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java deleted file mode 100644 index fb22ae1cae..0000000000 --- a/uma-server-webapp/src/main/java/org/mitre/openid/connect/service/impl/MITREidDataService_1_2.java +++ /dev/null @@ -1,1821 +0,0 @@ -package org.mitre.openid.connect.service.impl; -/******************************************************************************* - * Copyright 2015 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. - *******************************************************************************/ - - -import java.io.IOException; -import java.io.Serializable; -import java.text.ParseException; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.mitre.oauth2.model.AuthenticationHolderEntity; -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.OAuth2AccessTokenEntity; -import org.mitre.oauth2.model.OAuth2RefreshTokenEntity; -import org.mitre.oauth2.model.RegisteredClient; -import org.mitre.oauth2.model.SavedUserAuthentication; -import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; -import org.mitre.oauth2.repository.OAuth2ClientRepository; -import org.mitre.oauth2.repository.OAuth2TokenRepository; -import org.mitre.oauth2.repository.SystemScopeRepository; -import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; -import org.mitre.openid.connect.model.ApprovedSite; -import org.mitre.openid.connect.model.BlacklistedSite; -import org.mitre.openid.connect.model.WhitelistedSite; -import org.mitre.openid.connect.repository.ApprovedSiteRepository; -import org.mitre.openid.connect.repository.BlacklistedSiteRepository; -import org.mitre.openid.connect.repository.WhitelistedSiteRepository; -import org.mitre.openid.connect.service.MITREidDataService; -import org.mitre.uma.model.Claim; -import org.mitre.uma.model.Permission; -import org.mitre.uma.model.PermissionTicket; -import org.mitre.uma.model.Policy; -import org.mitre.uma.model.ResourceSet; -import org.mitre.uma.model.SavedRegisteredClient; -import org.mitre.uma.repository.PermissionRepository; -import org.mitre.uma.repository.ResourceSetRepository; -import org.mitre.uma.service.impl.JpaRegisteredClientService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.stereotype.Service; - -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; -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.JWTParser; - -import static org.mitre.util.JsonUtils.readMap; -import static org.mitre.util.JsonUtils.readSet; -import static org.mitre.util.JsonUtils.writeNullSafeArray; - -/** - * - * UMA EXPORT OVERRIDE - * - * Data service to import and export MITREid 1.2 configuration. - * - * @author jricher - * @author arielak - */ -@Service -@SuppressWarnings(value = {"unchecked"}) -public class MITREidDataService_1_2 extends MITREidDataServiceSupport implements MITREidDataService { - - private static final String REGISTERED_CLIENT = "registeredClient"; - private static final String DEFAULT_SCOPE = "defaultScope"; - private static final String STRUCTURED_PARAMETER = "structuredParameter"; - private static final String STRUCTURED = "structured"; - private static final String RESTRICTED = "restricted"; - private static final String ICON = "icon"; - private static final String DYNAMICALLY_REGISTERED = "dynamicallyRegistered"; - private static final String CLEAR_ACCESS_TOKENS_ON_REFRESH = "clearAccessTokensOnRefresh"; - private static final String REUSE_REFRESH_TOKEN = "reuseRefreshToken"; - private static final String ALLOW_INTROSPECTION = "allowIntrospection"; - private static final String DESCRIPTION = "description"; - private static final String REQUEST_URIS = "requestUris"; - private static final String POST_LOGOUT_REDIRECT_URI = "postLogoutRedirectUri"; - private static final String INTITATE_LOGIN_URI = "intitateLoginUri"; - private static final String DEFAULT_ACR_VALUES = "defaultACRValues"; - private static final String REQUIRE_AUTH_TIME = "requireAuthTime"; - private static final String DEFAULT_MAX_AGE = "defaultMaxAge"; - private static final String TOKEN_ENDPOINT_AUTH_SIGNING_ALG = "tokenEndpointAuthSigningAlg"; - private static final String USER_INFO_ENCRYPTED_RESPONSE_ENC = "userInfoEncryptedResponseEnc"; - private static final String USER_INFO_ENCRYPTED_RESPONSE_ALG = "userInfoEncryptedResponseAlg"; - private static final String USER_INFO_SIGNED_RESPONSE_ALG = "userInfoSignedResponseAlg"; - private static final String ID_TOKEN_ENCRYPTED_RESPONSE_ENC = "idTokenEncryptedResponseEnc"; - private static final String ID_TOKEN_ENCRYPTED_RESPONSE_ALG = "idTokenEncryptedResponseAlg"; - private static final String ID_TOKEN_SIGNED_RESPONSE_ALG = "idTokenSignedResponseAlg"; - private static final String REQUEST_OBJECT_SIGNING_ALG = "requestObjectSigningAlg"; - private static final String SUBJECT_TYPE = "subjectType"; - private static final String SECTOR_IDENTIFIER_URI = "sectorIdentifierUri"; - private static final String APPLICATION_TYPE = "applicationType"; - private static final String JWKS = "jwks"; - private static final String JWKS_URI = "jwksUri"; - private static final String POLICY_URI = "policyUri"; - private static final String GRANT_TYPES = "grantTypes"; - private static final String TOKEN_ENDPOINT_AUTH_METHOD = "tokenEndpointAuthMethod"; - private static final String TOS_URI = "tosUri"; - private static final String CONTACTS = "contacts"; - private static final String LOGO_URI = "logoUri"; - private static final String REDIRECT_URIS = "redirectUris"; - private static final String REFRESH_TOKEN_VALIDITY_SECONDS = "refreshTokenValiditySeconds"; - private static final String ACCESS_TOKEN_VALIDITY_SECONDS = "accessTokenValiditySeconds"; - private static final String SECRET = "secret"; - private static final String URI = "uri"; - private static final String CREATOR_USER_ID = "creatorUserId"; - private static final String APPROVED_ACCESS_TOKENS = "approvedAccessTokens"; - private static final String ALLOWED_SCOPES = "allowedScopes"; - private static final String USER_ID = "userId"; - private static final String TIMEOUT_DATE = "timeoutDate"; - private static final String CREATION_DATE = "creationDate"; - private static final String ACCESS_DATE = "accessDate"; - private static final String AUTHENTICATED = "authenticated"; - private static final String SOURCE_CLASS = "sourceClass"; - private static final String NAME = "name"; - private static final String SAVED_USER_AUTHENTICATION = "savedUserAuthentication"; - private static final String EXTENSIONS = "extensions"; - private static final String RESPONSE_TYPES = "responseTypes"; - private static final String REDIRECT_URI = "redirectUri"; - private static final String APPROVED = "approved"; - private static final String AUTHORITIES = "authorities"; - private static final String RESOURCE_IDS = "resourceIds"; - private static final String REQUEST_PARAMETERS = "requestParameters"; - private static final String TYPE = "type"; - private static final String SCOPE = "scope"; - private static final String ID_TOKEN_ID = "idTokenId"; - private static final String REFRESH_TOKEN_ID = "refreshTokenId"; - private static final String VALUE = "value"; - private static final String AUTHENTICATION_HOLDER_ID = "authenticationHolderId"; - private static final String CLIENT_ID = "clientId"; - private static final String EXPIRATION = "expiration"; - private static final String ID = "id"; - private static final String ICON_URI = "iconUri"; - private static final String OWNER = "owner"; - private static final String POLICIES = "policies"; - private static final String SCOPES = "scopes"; - private static final String CLAIMS_REQUIRED = "claimsRequired"; - private static final String ISSUER = "issuer"; - private static final String CLAIM_TOKEN_FORMAT = "claimTokenFormat"; - private static final String CLAIM_TYPE = "claimType"; - private static final String FRIENDLY_NAME = "friendlyName"; - private static final String PERMISSIONS = "permissions"; - private static final String RESOURCE_SET = "resourceSet"; - private static final String PERMISSION_TICKETS = "permissionTickets"; - private static final String PERMISSION = "permission"; - private static final String TICKET = "ticket"; - private static final String CLAIMS_SUPPLIED = "claimsSupplied"; - - private static final String SAVED_REGISTERED_CLIENTS = "savedRegisteredClients"; - private static final String RESOURCE_SETS = "resourceSets"; - - /** - * Logger for this class - */ - private static final Logger logger = LoggerFactory.getLogger(MITREidDataService_1_2.class); - @Autowired - private OAuth2ClientRepository clientRepository; - @Autowired - private ApprovedSiteRepository approvedSiteRepository; - @Autowired - private WhitelistedSiteRepository wlSiteRepository; - @Autowired - private BlacklistedSiteRepository blSiteRepository; - @Autowired - private AuthenticationHolderRepository authHolderRepository; - @Autowired - private OAuth2TokenRepository tokenRepository; - @Autowired - private SystemScopeRepository sysScopeRepository; - @Autowired - private JpaRegisteredClientService registeredClientService; - @Autowired - private ResourceSetRepository resourceSetRepository; - @Autowired - private PermissionRepository permissionRepository; - - /* (non-Javadoc) - * @see org.mitre.openid.connect.service.MITREidDataService#export(com.google.gson.stream.JsonWriter) - */ - @Override - public void exportData(JsonWriter writer) throws IOException { - - // version tag at the root - writer.name(MITREID_CONNECT_1_2); - - writer.beginObject(); - - // clients list - writer.name(CLIENTS); - writer.beginArray(); - writeClients(writer); - writer.endArray(); - - writer.name(GRANTS); - writer.beginArray(); - writeGrants(writer); - writer.endArray(); - - writer.name(WHITELISTEDSITES); - writer.beginArray(); - writeWhitelistedSites(writer); - writer.endArray(); - - writer.name(BLACKLISTEDSITES); - writer.beginArray(); - writeBlacklistedSites(writer); - writer.endArray(); - - writer.name(AUTHENTICATIONHOLDERS); - writer.beginArray(); - writeAuthenticationHolders(writer); - writer.endArray(); - - writer.name(ACCESSTOKENS); - writer.beginArray(); - writeAccessTokens(writer); - writer.endArray(); - - writer.name(REFRESHTOKENS); - writer.beginArray(); - writeRefreshTokens(writer); - writer.endArray(); - - writer.name(SYSTEMSCOPES); - writer.beginArray(); - writeSystemScopes(writer); - writer.endArray(); - - writer.name(SAVED_REGISTERED_CLIENTS); - writer.beginArray(); - writeSavedRegisteredClients(writer); - writer.endArray(); - - writer.name(RESOURCE_SETS); - writer.beginArray(); - writeResourceSets(writer); - writer.endArray(); - - writer.name(PERMISSION_TICKETS); - writer.beginArray(); - writePermissionTickets(writer); - writer.endArray(); - - writer.endObject(); // end mitreid-connect-1.2 - } - - /** - * @param writer - * @throws IOException - */ - private void writePermissionTickets(JsonWriter writer) throws IOException { - for (PermissionTicket ticket : permissionRepository.getAll()) { - writer.beginObject(); - - writer.name(CLAIMS_SUPPLIED); - writer.beginArray(); - for (Claim claim : ticket.getClaimsSupplied()) { - writer.beginObject(); - - writer.name(ISSUER); - writer.beginArray(); - for (String issuer : claim.getIssuer()) { - writer.value(issuer); - } - writer.endArray(); - writer.name(CLAIM_TOKEN_FORMAT); - writer.beginArray(); - for (String format : claim.getClaimTokenFormat()) { - writer.value(format); - } - writer.endArray(); - writer.name(CLAIM_TYPE).value(claim.getClaimType()); - writer.name(FRIENDLY_NAME).value(claim.getFriendlyName()); - writer.name(NAME).value(claim.getName()); - writer.name(VALUE).value(claim.getValue().toString()); - writer.endObject(); - } - writer.endArray(); - - writer.name(EXPIRATION).value(toUTCString(ticket.getExpiration())); - - writer.name(PERMISSION); - writer.beginObject(); - Permission p = ticket.getPermission(); - writer.name(RESOURCE_SET).value(p.getResourceSet().getId()); - writer.name(SCOPES); - writer.beginArray(); - for (String s : p.getScopes()) { - writer.value(s); - } - writer.endArray(); - writer.endObject(); - - writer.name(TICKET).value(ticket.getTicket()); - - writer.endObject(); - } - - - } - - /** - * @param writer - * @throws IOException - */ - private void writeResourceSets(JsonWriter writer) throws IOException { - for (ResourceSet rs : resourceSetRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(rs.getId()); - writer.name(CLIENT_ID).value(rs.getClientId()); - writer.name(ICON_URI).value(rs.getIconUri()); - writer.name(NAME).value(rs.getName()); - writer.name(TYPE).value(rs.getType()); - writer.name(URI).value(rs.getUri()); - writer.name(OWNER).value(rs.getOwner()); - writer.name(POLICIES); - writer.beginArray(); - for (Policy policy : rs.getPolicies()) { - writer.beginObject(); - writer.name(NAME).value(policy.getName()); - writer.name(SCOPES); - writer.beginArray(); - for (String scope : policy.getScopes()) { - writer.value(scope); - } - writer.endArray(); - writer.name(CLAIMS_REQUIRED); - writer.beginArray(); - for (Claim claim : policy.getClaimsRequired()) { - writer.beginObject(); - - writer.name(ISSUER); - writer.beginArray(); - for (String issuer : claim.getIssuer()) { - writer.value(issuer); - } - writer.endArray(); - writer.name(CLAIM_TOKEN_FORMAT); - writer.beginArray(); - for (String format : claim.getClaimTokenFormat()) { - writer.value(format); - } - writer.endArray(); - writer.name(CLAIM_TYPE).value(claim.getClaimType()); - writer.name(FRIENDLY_NAME).value(claim.getFriendlyName()); - writer.name(NAME).value(claim.getName()); - writer.name(VALUE).value(claim.getValue().toString()); - writer.endObject(); - } - writer.endArray(); - writer.endObject(); - } - writer.endArray(); - writer.name(SCOPES); - writer.beginArray(); - for (String scope : rs.getScopes()) { - writer.value(scope); - } - writer.endArray(); - writer.endObject(); - logger.debug("Finished writing resource set {}", rs.getId()); - } - - } - - /** - * @param writer - */ - private void writeSavedRegisteredClients(JsonWriter writer) throws IOException { - for (SavedRegisteredClient src : registeredClientService.getAll()) { - writer.beginObject(); - writer.name(ISSUER).value(src.getIssuer()); - writer.name(REGISTERED_CLIENT).value(src.getRegisteredClient().getSource().toString()); - writer.endObject(); - logger.debug("Wrote saved registered client {}", src.getId()); - } - logger.info("Done writing saved registered clients"); - } - - /** - * @param writer - */ - private void writeRefreshTokens(JsonWriter writer) throws IOException { - for (OAuth2RefreshTokenEntity token : tokenRepository.getAllRefreshTokens()) { - writer.beginObject(); - writer.name(ID).value(token.getId()); - writer.name(EXPIRATION).value(toUTCString(token.getExpiration())); - writer.name(CLIENT_ID) - .value((token.getClient() != null) ? token.getClient().getClientId() : null); - writer.name(AUTHENTICATION_HOLDER_ID) - .value((token.getAuthenticationHolder() != null) ? token.getAuthenticationHolder().getId() : null); - writer.name(VALUE).value(token.getValue()); - writer.endObject(); - logger.debug("Wrote refresh token {}", token.getId()); - } - logger.info("Done writing refresh tokens"); - } - - /** - * @param writer - */ - private void writeAccessTokens(JsonWriter writer) throws IOException { - for (OAuth2AccessTokenEntity token : tokenRepository.getAllAccessTokens()) { - writer.beginObject(); - writer.name(ID).value(token.getId()); - writer.name(EXPIRATION).value(toUTCString(token.getExpiration())); - writer.name(CLIENT_ID) - .value((token.getClient() != null) ? token.getClient().getClientId() : null); - writer.name(AUTHENTICATION_HOLDER_ID) - .value((token.getAuthenticationHolder() != null) ? token.getAuthenticationHolder().getId() : null); - writer.name(REFRESH_TOKEN_ID) - .value((token.getRefreshToken() != null) ? token.getRefreshToken().getId() : null); - writer.name(ID_TOKEN_ID) - .value((token.getIdToken() != null) ? token.getIdToken().getId() : null); - writer.name(SCOPE); - writer.beginArray(); - for (String s : token.getScope()) { - writer.value(s); - } - writer.endArray(); - writer.name(PERMISSIONS); - writer.beginArray(); - for (Permission p : token.getPermissions()) { - writer.beginObject(); - writer.name(RESOURCE_SET).value(p.getResourceSet().getId()); - writer.name(SCOPES); - writer.beginArray(); - for (String s : p.getScopes()) { - writer.value(s); - } - writer.endArray(); - writer.endObject(); - } - writer.endArray(); - - writer.name(TYPE).value(token.getTokenType()); - writer.name(VALUE).value(token.getValue()); - writer.endObject(); - logger.debug("Wrote access token {}", token.getId()); - } - logger.info("Done writing access tokens"); - } - - /** - * @param writer - */ - private void writeAuthenticationHolders(JsonWriter writer) throws IOException { - for (AuthenticationHolderEntity holder : authHolderRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(holder.getId()); - - writer.name(REQUEST_PARAMETERS); - writer.beginObject(); - for (Entry entry : holder.getRequestParameters().entrySet()) { - writer.name(entry.getKey()).value(entry.getValue()); - } - writer.endObject(); - writer.name(CLIENT_ID).value(holder.getClientId()); - Set scope = holder.getScope(); - writer.name(SCOPE); - writer.beginArray(); - for (String s : scope) { - writer.value(s); - } - writer.endArray(); - writer.name(RESOURCE_IDS); - writer.beginArray(); - if (holder.getResourceIds() != null) { - for (String s : holder.getResourceIds()) { - writer.value(s); - } - } - writer.endArray(); - writer.name(AUTHORITIES); - writer.beginArray(); - for (GrantedAuthority authority : holder.getAuthorities()) { - writer.value(authority.getAuthority()); - } - writer.endArray(); - writer.name(APPROVED).value(holder.isApproved()); - writer.name(REDIRECT_URI).value(holder.getRedirectUri()); - writer.name(RESPONSE_TYPES); - writer.beginArray(); - for (String s : holder.getResponseTypes()) { - writer.value(s); - } - writer.endArray(); - writer.name(EXTENSIONS); - writer.beginObject(); - for (Entry entry : holder.getExtensions().entrySet()) { - // while the extension map itself is Serializable, we enforce storage of Strings - if (entry.getValue() instanceof String) { - writer.name(entry.getKey()).value((String) entry.getValue()); - } else { - logger.warn("Skipping non-string extension: " + entry); - } - } - writer.endObject(); - - writer.name(SAVED_USER_AUTHENTICATION); - if (holder.getUserAuth() != null) { - writer.beginObject(); - writer.name(NAME).value(holder.getUserAuth().getName()); - writer.name(SOURCE_CLASS).value(holder.getUserAuth().getSourceClass()); - writer.name(AUTHENTICATED).value(holder.getUserAuth().isAuthenticated()); - writer.name(AUTHORITIES); - writer.beginArray(); - for (GrantedAuthority authority : holder.getUserAuth().getAuthorities()) { - writer.value(authority.getAuthority()); - } - writer.endArray(); - - writer.endObject(); - } else { - writer.nullValue(); - } - - - writer.endObject(); - logger.debug("Wrote authentication holder {}", holder.getId()); - } - logger.info("Done writing authentication holders"); - } - - /** - * @param writer - */ - private void writeGrants(JsonWriter writer) throws IOException { - for (ApprovedSite site : approvedSiteRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(site.getId()); - writer.name(ACCESS_DATE).value(toUTCString(site.getAccessDate())); - writer.name(CLIENT_ID).value(site.getClientId()); - writer.name(CREATION_DATE).value(toUTCString(site.getCreationDate())); - writer.name(TIMEOUT_DATE).value(toUTCString(site.getTimeoutDate())); - writer.name(USER_ID).value(site.getUserId()); - writer.name(ALLOWED_SCOPES); - writeNullSafeArray(writer, site.getAllowedScopes()); - Set tokens = site.getApprovedAccessTokens(); - writer.name(APPROVED_ACCESS_TOKENS); - writer.beginArray(); - for (OAuth2AccessTokenEntity token : tokens) { - writer.value(token.getId()); - } - writer.endArray(); - writer.endObject(); - logger.debug("Wrote grant {}", site.getId()); - } - logger.info("Done writing grants"); - } - - /** - * @param writer - */ - private void writeWhitelistedSites(JsonWriter writer) throws IOException { - for (WhitelistedSite wlSite : wlSiteRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(wlSite.getId()); - writer.name(CLIENT_ID).value(wlSite.getClientId()); - writer.name(CREATOR_USER_ID).value(wlSite.getCreatorUserId()); - writer.name(ALLOWED_SCOPES); - writeNullSafeArray(writer, wlSite.getAllowedScopes()); - writer.endObject(); - logger.debug("Wrote whitelisted site {}", wlSite.getId()); - } - logger.info("Done writing whitelisted sites"); - } - - /** - * @param writer - */ - private void writeBlacklistedSites(JsonWriter writer) throws IOException { - for (BlacklistedSite blSite : blSiteRepository.getAll()) { - writer.beginObject(); - writer.name(ID).value(blSite.getId()); - writer.name(URI).value(blSite.getUri()); - writer.endObject(); - logger.debug("Wrote blacklisted site {}", blSite.getId()); - } - logger.info("Done writing blacklisted sites"); - } - - /** - * @param writer - */ - private void writeClients(JsonWriter writer) { - for (ClientDetailsEntity client : clientRepository.getAllClients()) { - try { - writer.beginObject(); - writer.name(CLIENT_ID).value(client.getClientId()); - writer.name(RESOURCE_IDS); - writeNullSafeArray(writer, client.getResourceIds()); - - writer.name(SECRET).value(client.getClientSecret()); - - writer.name(SCOPE); - writeNullSafeArray(writer, client.getScope()); - - writer.name(AUTHORITIES); - writer.beginArray(); - for (GrantedAuthority authority : client.getAuthorities()) { - writer.value(authority.getAuthority()); - } - writer.endArray(); - writer.name(ACCESS_TOKEN_VALIDITY_SECONDS).value(client.getAccessTokenValiditySeconds()); - writer.name(REFRESH_TOKEN_VALIDITY_SECONDS).value(client.getRefreshTokenValiditySeconds()); - writer.name(REDIRECT_URIS); - writeNullSafeArray(writer, client.getRedirectUris()); - writer.name(NAME).value(client.getClientName()); - writer.name(URI).value(client.getClientUri()); - writer.name(LOGO_URI).value(client.getLogoUri()); - writer.name(CONTACTS); - writeNullSafeArray(writer, client.getContacts()); - writer.name(TOS_URI).value(client.getTosUri()); - writer.name(TOKEN_ENDPOINT_AUTH_METHOD) - .value((client.getTokenEndpointAuthMethod() != null) ? client.getTokenEndpointAuthMethod().getValue() : null); - writer.name(GRANT_TYPES); - writer.beginArray(); - for (String s : client.getGrantTypes()) { - writer.value(s); - } - writer.endArray(); - writer.name(RESPONSE_TYPES); - writer.beginArray(); - for (String s : client.getResponseTypes()) { - writer.value(s); - } - writer.endArray(); - writer.name(POLICY_URI).value(client.getPolicyUri()); - writer.name(JWKS_URI).value(client.getJwksUri()); - writer.name(JWKS).value((client.getJwks() != null) ? client.getJwks().toString() : null); - writer.name(APPLICATION_TYPE) - .value((client.getApplicationType() != null) ? client.getApplicationType().getValue() : null); - writer.name(SECTOR_IDENTIFIER_URI).value(client.getSectorIdentifierUri()); - writer.name(SUBJECT_TYPE) - .value((client.getSubjectType() != null) ? client.getSubjectType().getValue() : null); - writer.name(REQUEST_OBJECT_SIGNING_ALG) - .value((client.getRequestObjectSigningAlg() != null) ? client.getRequestObjectSigningAlg().getName() : null); - writer.name(ID_TOKEN_SIGNED_RESPONSE_ALG) - .value((client.getIdTokenSignedResponseAlg() != null) ? client.getIdTokenSignedResponseAlg().getName() : null); - writer.name(ID_TOKEN_ENCRYPTED_RESPONSE_ALG) - .value((client.getIdTokenEncryptedResponseAlg() != null) ? client.getIdTokenEncryptedResponseAlg().getName() : null); - writer.name(ID_TOKEN_ENCRYPTED_RESPONSE_ENC) - .value((client.getIdTokenEncryptedResponseEnc() != null) ? client.getIdTokenEncryptedResponseEnc().getName() : null); - writer.name(USER_INFO_SIGNED_RESPONSE_ALG) - .value((client.getUserInfoSignedResponseAlg() != null) ? client.getUserInfoSignedResponseAlg().getName() : null); - writer.name(USER_INFO_ENCRYPTED_RESPONSE_ALG) - .value((client.getUserInfoEncryptedResponseAlg() != null) ? client.getUserInfoEncryptedResponseAlg().getName() : null); - writer.name(USER_INFO_ENCRYPTED_RESPONSE_ENC) - .value((client.getUserInfoEncryptedResponseEnc() != null) ? client.getUserInfoEncryptedResponseEnc().getName() : null); - writer.name(TOKEN_ENDPOINT_AUTH_SIGNING_ALG) - .value((client.getTokenEndpointAuthSigningAlg() != null) ? client.getTokenEndpointAuthSigningAlg().getName() : null); - writer.name(DEFAULT_MAX_AGE).value(client.getDefaultMaxAge()); - Boolean requireAuthTime = null; - try { - requireAuthTime = client.getRequireAuthTime(); - } catch (NullPointerException e) { - } - if (requireAuthTime != null) { - writer.name(REQUIRE_AUTH_TIME).value(requireAuthTime); - } - writer.name(DEFAULT_ACR_VALUES); - writeNullSafeArray(writer, client.getDefaultACRvalues()); - writer.name(INTITATE_LOGIN_URI).value(client.getInitiateLoginUri()); - writer.name(POST_LOGOUT_REDIRECT_URI); - writeNullSafeArray(writer, client.getPostLogoutRedirectUris()); - writer.name(REQUEST_URIS); - writeNullSafeArray(writer, client.getRequestUris()); - writer.name(DESCRIPTION).value(client.getClientDescription()); - writer.name(ALLOW_INTROSPECTION).value(client.isAllowIntrospection()); - writer.name(REUSE_REFRESH_TOKEN).value(client.isReuseRefreshToken()); - writer.name(CLEAR_ACCESS_TOKENS_ON_REFRESH).value(client.isClearAccessTokensOnRefresh()); - writer.name(DYNAMICALLY_REGISTERED).value(client.isDynamicallyRegistered()); - writer.endObject(); - logger.debug("Wrote client {}", client.getId()); - } catch (IOException ex) { - logger.error("Unable to write client {}", client.getId(), ex); - } - } - logger.info("Done writing clients"); - } - - /** - * @param writer - */ - private void writeSystemScopes(JsonWriter writer) { - for (SystemScope sysScope : sysScopeRepository.getAll()) { - try { - writer.beginObject(); - writer.name(ID).value(sysScope.getId()); - writer.name(DESCRIPTION).value(sysScope.getDescription()); - writer.name(ICON).value(sysScope.getIcon()); - writer.name(VALUE).value(sysScope.getValue()); - writer.name(RESTRICTED).value(sysScope.isRestricted()); - writer.name(STRUCTURED).value(sysScope.isStructured()); - writer.name(STRUCTURED_PARAMETER).value(sysScope.getStructuredParamDescription()); - writer.name(DEFAULT_SCOPE).value(sysScope.isDefaultScope()); - writer.endObject(); - logger.debug("Wrote system scope {}", sysScope.getId()); - } catch (IOException ex) { - logger.error("Unable to write system scope {}", sysScope.getId(), ex); - } - } - logger.info("Done writing system scopes"); - } - - /* (non-Javadoc) - * @see org.mitre.openid.connect.service.MITREidDataService#importData(com.google.gson.stream.JsonReader) - */ - @Override - public void importData(JsonReader reader) throws IOException { - - logger.info("Reading configuration for 1.2"); - - // this *HAS* to start as an object - reader.beginObject(); - - while (reader.hasNext()) { - JsonToken tok = reader.peek(); - switch (tok) { - case NAME: - String name = reader.nextName(); - // find out which member it is - if (name.equals(CLIENTS)) { - readClients(reader); - } else if (name.equals(GRANTS)) { - readGrants(reader); - } else if (name.equals(WHITELISTEDSITES)) { - readWhitelistedSites(reader); - } else if (name.equals(BLACKLISTEDSITES)) { - readBlacklistedSites(reader); - } else if (name.equals(AUTHENTICATIONHOLDERS)) { - readAuthenticationHolders(reader); - } else if (name.equals(ACCESSTOKENS)) { - readAccessTokens(reader); - } else if (name.equals(REFRESHTOKENS)) { - readRefreshTokens(reader); - } else if (name.equals(SYSTEMSCOPES)) { - readSystemScopes(reader); - } else if (name.equals(SAVED_REGISTERED_CLIENTS)) { - readSavedRegisteredClients(reader); - } else if (name.equals(RESOURCE_SETS)) { - readResourceSets(reader); - } else if (name.equals(PERMISSION_TICKETS)) { - readPermissionTickets(reader); - } else { - // unknown token, skip it - reader.skipValue(); - } - break; - case END_OBJECT: - // the object ended, we're done here - reader.endObject(); - continue; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - fixObjectReferences(); - } - - - /** - * @param reader - */ - private void readPermissionTickets(JsonReader reader) throws IOException { - JsonParser parser = new JsonParser(); - reader.beginArray(); - while (reader.hasNext()) { - PermissionTicket ticket = new PermissionTicket(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(CLAIMS_SUPPLIED)) { - Set claimsSupplied = new HashSet<>(); - reader.beginArray(); - while (reader.hasNext()) { - Claim c = new Claim(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String cname = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (cname.equals(ISSUER)) { - c.setIssuer(readSet(reader)); - } else if (cname.equals(CLAIM_TOKEN_FORMAT)) { - c.setClaimTokenFormat(readSet(reader)); - } else if (cname.equals(CLAIM_TYPE)) { - c.setClaimType(reader.nextString()); - } else if (cname.equals(FRIENDLY_NAME)) { - c.setFriendlyName(reader.nextString()); - } else if (cname.equals(NAME)) { - c.setName(reader.nextString()); - } else if (cname.equals(VALUE)) { - JsonElement e = parser.parse(reader.nextString()); - c.setValue(e); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - claimsSupplied.add(c); - } - reader.endArray(); - ticket.setClaimsSupplied(claimsSupplied); - } else if (name.equals(EXPIRATION)) { - ticket.setExpiration(utcToDate(reader.nextString())); - } else if (name.equals(PERMISSION)) { - Permission p = new Permission(); - Long rsid = null; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String pname = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (pname.equals(RESOURCE_SET)) { - rsid = reader.nextLong(); - } else if (pname.equals(SCOPES)) { - p.setScopes(readSet(reader)); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Permission saved = permissionRepository.saveRawPermission(p); - permissionToResourceRefs.put(saved.getId(), rsid); - ticket.setPermission(saved); - } else if (name.equals(TICKET)) { - ticket.setTicket(reader.nextString()); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - permissionRepository.save(ticket); - } - reader.endArray(); - } - - - private Map resourceSetOldToNewIdMap = new HashMap<>(); - - /** - * @param reader - */ - private void readResourceSets(JsonReader reader) throws IOException { - JsonParser parser = new JsonParser(); - reader.beginArray(); - while (reader.hasNext()) { - Long oldId = null; - ResourceSet rs = new ResourceSet(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - oldId = reader.nextLong(); - } else if (name.equals(CLIENT_ID)) { - rs.setClientId(reader.nextString()); - } else if (name.equals(ICON_URI)) { - rs.setIconUri(reader.nextString()); - } else if (name.equals(NAME)) { - rs.setName(reader.nextString()); - } else if (name.equals(TYPE)) { - rs.setType(reader.nextString()); - } else if (name.equals(URI)) { - rs.setUri(reader.nextString()); - } else if (name.equals(OWNER)) { - rs.setOwner(reader.nextString()); - } else if (name.equals(POLICIES)) { - Set policies = new HashSet<>(); - reader.beginArray(); - while (reader.hasNext()) { - Policy p = new Policy(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String pname = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (pname.equals(NAME)) { - p.setName(reader.nextString()); - } else if (pname.equals(SCOPES)) { - p.setScopes(readSet(reader)); - } else if (pname.equals(CLAIMS_REQUIRED)) { - Set claimsRequired = new HashSet<>(); - reader.beginArray(); - while (reader.hasNext()) { - Claim c = new Claim(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String cname = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (cname.equals(ISSUER)) { - c.setIssuer(readSet(reader)); - } else if (cname.equals(CLAIM_TOKEN_FORMAT)) { - c.setClaimTokenFormat(readSet(reader)); - } else if (cname.equals(CLAIM_TYPE)) { - c.setClaimType(reader.nextString()); - } else if (cname.equals(FRIENDLY_NAME)) { - c.setFriendlyName(reader.nextString()); - } else if (cname.equals(NAME)) { - c.setName(reader.nextString()); - } else if (cname.equals(VALUE)) { - JsonElement e = parser.parse(reader.nextString()); - c.setValue(e); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - claimsRequired.add(c); - } - reader.endArray(); - p.setClaimsRequired(claimsRequired); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - policies.add(p); - } - reader.endArray(); - rs.setPolicies(policies); - } else if (name.equals(SCOPES)) { - rs.setScopes(readSet(reader)); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Long newId = resourceSetRepository.save(rs).getId(); - resourceSetOldToNewIdMap.put(oldId, newId); - } - reader.endArray(); - logger.info("Done reading resource sets"); - } - - /** - * @param reader - */ - private void readSavedRegisteredClients(JsonReader reader) throws IOException{ - reader.beginArray(); - while (reader.hasNext()) { - String issuer = null; - String clientString = null; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ISSUER)) { - issuer = reader.nextString(); - } else if (name.equals(REGISTERED_CLIENT)) { - clientString = reader.nextString(); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(clientString); - registeredClientService.save(issuer, client); - logger.debug("Saved registered client"); - } - reader.endArray(); - logger.info("Done reading saved registered clients"); - } - - private Map refreshTokenToClientRefs = new HashMap(); - private Map refreshTokenToAuthHolderRefs = new HashMap(); - private Map refreshTokenOldToNewIdMap = new HashMap(); - /** - * @param reader - * @throws IOException - */ - private void readRefreshTokens(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - OAuth2RefreshTokenEntity token = new OAuth2RefreshTokenEntity(); - reader.beginObject(); - Long currentId = null; - String clientId = null; - Long authHolderId = null; - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(EXPIRATION)) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals(VALUE)) { - String value = reader.nextString(); - try { - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); - } - } else if (name.equals(CLIENT_ID)) { - clientId = reader.nextString(); - } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { - authHolderId = reader.nextLong(); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Long newId = tokenRepository.saveRefreshToken(token).getId(); - refreshTokenToClientRefs.put(currentId, clientId); - refreshTokenToAuthHolderRefs.put(currentId, authHolderId); - refreshTokenOldToNewIdMap.put(currentId, newId); - logger.debug("Read refresh token {}", currentId); - } - reader.endArray(); - logger.info("Done reading refresh tokens"); - } - private Map accessTokenToClientRefs = new HashMap(); - private Map accessTokenToAuthHolderRefs = new HashMap(); - private Map accessTokenToRefreshTokenRefs = new HashMap(); - private Map accessTokenToIdTokenRefs = new HashMap(); - private Map accessTokenOldToNewIdMap = new HashMap(); - private Map permissionToResourceRefs = new HashMap<>(); - - /** - * @param reader - * @throws IOException - */ - /** - * @param reader - * @throws IOException - */ - private void readAccessTokens(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); - reader.beginObject(); - Long currentId = null; - String clientId = null; - Long authHolderId = null; - Long refreshTokenId = null; - Long idTokenId = null; - Set permissions = new HashSet<>(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(EXPIRATION)) { - Date date = utcToDate(reader.nextString()); - token.setExpiration(date); - } else if (name.equals(VALUE)) { - String value = reader.nextString(); - try { - // all tokens are JWTs - token.setJwt(JWTParser.parse(value)); - } catch (ParseException ex) { - logger.error("Unable to set refresh token value to {}", value, ex); - } - } else if (name.equals(CLIENT_ID)) { - clientId = reader.nextString(); - } else if (name.equals(AUTHENTICATION_HOLDER_ID)) { - authHolderId = reader.nextLong(); - } else if (name.equals(REFRESH_TOKEN_ID)) { - refreshTokenId = reader.nextLong(); - } else if (name.equals(ID_TOKEN_ID)) { - idTokenId = reader.nextLong(); - } else if (name.equals(SCOPE)) { - Set scope = readSet(reader); - token.setScope(scope); - } else if (name.equals(PERMISSIONS)) { - reader.beginArray(); - while (reader.hasNext()) { - Permission p = new Permission(); - Long rsid = null; - Set scope = new HashSet<>(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String pname = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (pname.equals(RESOURCE_SET)) { - rsid = reader.nextLong(); - } else if (pname.equals(SCOPES)) { - scope = readSet(reader); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - p.setScopes(scope); - Permission saved = permissionRepository.saveRawPermission(p); - permissionToResourceRefs.put(saved.getId(), rsid); - permissions.add(saved); - } - reader.endArray(); - token.setPermissions(permissions); - } else if (name.equals(TYPE)) { - token.setTokenType(reader.nextString()); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Long newId = tokenRepository.saveAccessToken(token).getId(); - accessTokenToClientRefs.put(currentId, clientId); - accessTokenToAuthHolderRefs.put(currentId, authHolderId); - if (refreshTokenId != null) { - accessTokenToRefreshTokenRefs.put(currentId, refreshTokenId); - } - if (idTokenId != null) { - accessTokenToIdTokenRefs.put(currentId, idTokenId); - } - accessTokenOldToNewIdMap.put(currentId, newId); - logger.debug("Read access token {}", currentId); - } - reader.endArray(); - logger.info("Done reading access tokens"); - } - - - private Map authHolderOldToNewIdMap = new HashMap(); - - /** - * @param reader - * @throws IOException - */ - private void readAuthenticationHolders(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - AuthenticationHolderEntity ahe = new AuthenticationHolderEntity(); - reader.beginObject(); - Long currentId = null; - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(REQUEST_PARAMETERS)) { - ahe.setRequestParameters(readMap(reader)); - } else if (name.equals(CLIENT_ID)) { - ahe.setClientId(reader.nextString()); - } else if (name.equals(SCOPE)) { - ahe.setScope(readSet(reader)); - } else if (name.equals(RESOURCE_IDS)) { - ahe.setResourceIds(readSet(reader)); - } else if (name.equals(AUTHORITIES)) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); - } - ahe.setAuthorities(authorities); - } else if (name.equals(APPROVED)) { - ahe.setApproved(reader.nextBoolean()); - } else if (name.equals(REDIRECT_URI)) { - ahe.setRedirectUri(reader.nextString()); - } else if (name.equals(RESPONSE_TYPES)) { - ahe.setResponseTypes(readSet(reader)); - } else if (name.equals(EXTENSIONS)) { - ahe.setExtensions(readMap(reader)); - } else if (name.equals(SAVED_USER_AUTHENTICATION)) { - ahe.setUserAuth(readSavedUserAuthentication(reader)); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Long newId = authHolderRepository.save(ahe).getId(); - authHolderOldToNewIdMap.put(currentId, newId); - logger.debug("Read authentication holder {}", currentId); - } - reader.endArray(); - logger.info("Done reading authentication holders"); - } - - /** - * @param reader - * @return - * @throws IOException - */ - private SavedUserAuthentication readSavedUserAuthentication(JsonReader reader) throws IOException { - SavedUserAuthentication savedUserAuth = new SavedUserAuthentication(); - reader.beginObject(); - - while (reader.hasNext()) { - switch(reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(NAME)) { - savedUserAuth.setName(reader.nextString()); - } else if (name.equals(SOURCE_CLASS)) { - savedUserAuth.setSourceClass(reader.nextString()); - } else if (name.equals(AUTHENTICATED)) { - savedUserAuth.setAuthenticated(reader.nextBoolean()); - } else if (name.equals(AUTHORITIES)) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); - } - savedUserAuth.setAuthorities(authorities); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - - reader.endObject(); - return savedUserAuth; - } - - private Map grantOldToNewIdMap = new HashMap<>(); - private Map> grantToAccessTokensRefs = new HashMap<>(); - - /** - * @param reader - * @throws IOException - */ - private void readGrants(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - ApprovedSite site = new ApprovedSite(); - Long currentId = null; - Set tokenIds = null; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(ACCESS_DATE)) { - Date date = utcToDate(reader.nextString()); - site.setAccessDate(date); - } else if (name.equals(CLIENT_ID)) { - site.setClientId(reader.nextString()); - } else if (name.equals(CREATION_DATE)) { - Date date = utcToDate(reader.nextString()); - site.setCreationDate(date); - } else if (name.equals(TIMEOUT_DATE)) { - Date date = utcToDate(reader.nextString()); - site.setTimeoutDate(date); - } else if (name.equals(USER_ID)) { - site.setUserId(reader.nextString()); - } else if (name.equals(ALLOWED_SCOPES)) { - Set allowedScopes = readSet(reader); - site.setAllowedScopes(allowedScopes); - } else if (name.equals(APPROVED_ACCESS_TOKENS)) { - tokenIds = readSet(reader); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Long newId = approvedSiteRepository.save(site).getId(); - grantOldToNewIdMap.put(currentId, newId); - if (tokenIds != null) { - grantToAccessTokensRefs.put(currentId, tokenIds); - } - logger.debug("Read grant {}", currentId); - } - reader.endArray(); - logger.info("Done reading grants"); - } - - private Map whitelistedSiteOldToNewIdMap = new HashMap(); - - /** - * @param reader - * @throws IOException - */ - private void readWhitelistedSites(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - WhitelistedSite wlSite = new WhitelistedSite(); - Long currentId = null; - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals(ID)) { - currentId = reader.nextLong(); - } else if (name.equals(CLIENT_ID)) { - wlSite.setClientId(reader.nextString()); - } else if (name.equals(CREATOR_USER_ID)) { - wlSite.setCreatorUserId(reader.nextString()); - } else if (name.equals(ALLOWED_SCOPES)) { - Set allowedScopes = readSet(reader); - wlSite.setAllowedScopes(allowedScopes); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - Long newId = wlSiteRepository.save(wlSite).getId(); - whitelistedSiteOldToNewIdMap.put(currentId, newId); - } - reader.endArray(); - logger.info("Done reading whitelisted sites"); - } - - /** - * @param reader - * @throws IOException - */ - private void readBlacklistedSites(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - BlacklistedSite blSite = new BlacklistedSite(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (name.equals(ID)) { - reader.skipValue(); - } else if (name.equals(URI)) { - blSite.setUri(reader.nextString()); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - blSiteRepository.save(blSite); - } - reader.endArray(); - logger.info("Done reading blacklisted sites"); - } - - /** - * @param reader - * @throws IOException - */ - private void readClients(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - ClientDetailsEntity client = new ClientDetailsEntity(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(CLIENT_ID)) { - client.setClientId(reader.nextString()); - } else if (name.equals(RESOURCE_IDS)) { - Set resourceIds = readSet(reader); - client.setResourceIds(resourceIds); - } else if (name.equals(SECRET)) { - client.setClientSecret(reader.nextString()); - } else if (name.equals(SCOPE)) { - Set scope = readSet(reader); - client.setScope(scope); - } else if (name.equals(AUTHORITIES)) { - Set authorityStrs = readSet(reader); - Set authorities = new HashSet(); - for (String s : authorityStrs) { - GrantedAuthority ga = new SimpleGrantedAuthority(s); - authorities.add(ga); - } - client.setAuthorities(authorities); - } else if (name.equals(ACCESS_TOKEN_VALIDITY_SECONDS)) { - client.setAccessTokenValiditySeconds(reader.nextInt()); - } else if (name.equals(REFRESH_TOKEN_VALIDITY_SECONDS)) { - client.setRefreshTokenValiditySeconds(reader.nextInt()); - } else if (name.equals(REDIRECT_URIS)) { - Set redirectUris = readSet(reader); - client.setRedirectUris(redirectUris); - } else if (name.equals(NAME)) { - client.setClientName(reader.nextString()); - } else if (name.equals(URI)) { - client.setClientUri(reader.nextString()); - } else if (name.equals(LOGO_URI)) { - client.setLogoUri(reader.nextString()); - } else if (name.equals(CONTACTS)) { - Set contacts = readSet(reader); - client.setContacts(contacts); - } else if (name.equals(TOS_URI)) { - client.setTosUri(reader.nextString()); - } else if (name.equals(TOKEN_ENDPOINT_AUTH_METHOD)) { - AuthMethod am = AuthMethod.getByValue(reader.nextString()); - client.setTokenEndpointAuthMethod(am); - } else if (name.equals(GRANT_TYPES)) { - Set grantTypes = readSet(reader); - client.setGrantTypes(grantTypes); - } else if (name.equals(RESPONSE_TYPES)) { - Set responseTypes = readSet(reader); - client.setResponseTypes(responseTypes); - } else if (name.equals(POLICY_URI)) { - client.setPolicyUri(reader.nextString()); - } else if (name.equals(APPLICATION_TYPE)) { - AppType appType = AppType.getByValue(reader.nextString()); - client.setApplicationType(appType); - } else if (name.equals(SECTOR_IDENTIFIER_URI)) { - client.setSectorIdentifierUri(reader.nextString()); - } else if (name.equals(SUBJECT_TYPE)) { - SubjectType st = SubjectType.getByValue(reader.nextString()); - client.setSubjectType(st); - } else if (name.equals(JWKS_URI)) { - client.setJwksUri(reader.nextString()); - } else if (name.equals(JWKS)) { - try { - client.setJwks(JWKSet.parse(reader.nextString())); - } catch (ParseException e) { - logger.error("Couldn't parse JWK Set", e); - } - } else if (name.equals(REQUEST_OBJECT_SIGNING_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setRequestObjectSigningAlg(alg); - } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ALG)) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setUserInfoEncryptedResponseAlg(alg); - } else if (name.equals(USER_INFO_ENCRYPTED_RESPONSE_ENC)) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setUserInfoEncryptedResponseEnc(alg); - } else if (name.equals(USER_INFO_SIGNED_RESPONSE_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setUserInfoSignedResponseAlg(alg); - } else if (name.equals(ID_TOKEN_SIGNED_RESPONSE_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setIdTokenSignedResponseAlg(alg); - } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ALG)) { - JWEAlgorithm alg = JWEAlgorithm.parse(reader.nextString()); - client.setIdTokenEncryptedResponseAlg(alg); - } else if (name.equals(ID_TOKEN_ENCRYPTED_RESPONSE_ENC)) { - EncryptionMethod alg = EncryptionMethod.parse(reader.nextString()); - client.setIdTokenEncryptedResponseEnc(alg); - } else if (name.equals(TOKEN_ENDPOINT_AUTH_SIGNING_ALG)) { - JWSAlgorithm alg = JWSAlgorithm.parse(reader.nextString()); - client.setTokenEndpointAuthSigningAlg(alg); - } else if (name.equals(DEFAULT_MAX_AGE)) { - client.setDefaultMaxAge(reader.nextInt()); - } else if (name.equals(REQUIRE_AUTH_TIME)) { - client.setRequireAuthTime(reader.nextBoolean()); - } else if (name.equals(DEFAULT_ACR_VALUES)) { - Set defaultACRvalues = readSet(reader); - client.setDefaultACRvalues(defaultACRvalues); - } else if (name.equals("initiateLoginUri")) { - client.setInitiateLoginUri(reader.nextString()); - } else if (name.equals(POST_LOGOUT_REDIRECT_URI)) { - Set postLogoutUris = readSet(reader); - client.setPostLogoutRedirectUris(postLogoutUris); - } else if (name.equals(REQUEST_URIS)) { - Set requestUris = readSet(reader); - client.setRequestUris(requestUris); - } else if (name.equals(DESCRIPTION)) { - client.setClientDescription(reader.nextString()); - } else if (name.equals(ALLOW_INTROSPECTION)) { - client.setAllowIntrospection(reader.nextBoolean()); - } else if (name.equals(REUSE_REFRESH_TOKEN)) { - client.setReuseRefreshToken(reader.nextBoolean()); - } else if (name.equals(CLEAR_ACCESS_TOKENS_ON_REFRESH)) { - client.setClearAccessTokensOnRefresh(reader.nextBoolean()); - } else if (name.equals(DYNAMICALLY_REGISTERED)) { - client.setDynamicallyRegistered(reader.nextBoolean()); - } else { - logger.debug("Found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - clientRepository.saveClient(client); - } - reader.endArray(); - logger.info("Done reading clients"); - } - - /** - * Read the list of system scopes from the reader and insert them into the - * scope repository. - * - * @param reader - * @throws IOException - */ - private void readSystemScopes(JsonReader reader) throws IOException { - reader.beginArray(); - while (reader.hasNext()) { - SystemScope scope = new SystemScope(); - reader.beginObject(); - while (reader.hasNext()) { - switch (reader.peek()) { - case END_OBJECT: - continue; - case NAME: - String name = reader.nextName(); - if (reader.peek() == JsonToken.NULL) { - reader.skipValue(); - } else if (name.equals(VALUE)) { - scope.setValue(reader.nextString()); - } else if (name.equals(DESCRIPTION)) { - scope.setDescription(reader.nextString()); - } else if (name.equals(RESTRICTED)) { - scope.setRestricted(reader.nextBoolean()); - } else if (name.equals(DEFAULT_SCOPE)) { - scope.setDefaultScope(reader.nextBoolean()); - } else if (name.equals(ICON)) { - scope.setIcon(reader.nextString()); - } else if (name.equals(STRUCTURED)) { - scope.setStructured(reader.nextBoolean()); - } else if (name.equals(STRUCTURED_PARAMETER)) { - scope.setStructuredParamDescription(reader.nextString()); - } else { - logger.debug("found unexpected entry"); - reader.skipValue(); - } - break; - default: - logger.debug("Found unexpected entry"); - reader.skipValue(); - continue; - } - } - reader.endObject(); - sysScopeRepository.save(scope); - } - reader.endArray(); - logger.info("Done reading system scopes"); - } - - private void fixObjectReferences() { - logger.info("Fixing object references..."); - for (Long oldRefreshTokenId : refreshTokenToClientRefs.keySet()) { - String clientRef = refreshTokenToClientRefs.get(oldRefreshTokenId); - ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); - OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); - refreshToken.setClient(client); - tokenRepository.saveRefreshToken(refreshToken); - } - refreshTokenToClientRefs.clear(); - for (Long oldRefreshTokenId : refreshTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = refreshTokenToAuthHolderRefs.get(oldRefreshTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); - AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); - OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); - refreshToken.setAuthenticationHolder(authHolder); - tokenRepository.saveRefreshToken(refreshToken); - } - refreshTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToClientRefs.keySet()) { - String clientRef = accessTokenToClientRefs.get(oldAccessTokenId); - ClientDetailsEntity client = clientRepository.getClientByClientId(clientRef); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setClient(client); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToClientRefs.clear(); - for (Long oldAccessTokenId : accessTokenToAuthHolderRefs.keySet()) { - Long oldAuthHolderId = accessTokenToAuthHolderRefs.get(oldAccessTokenId); - Long newAuthHolderId = authHolderOldToNewIdMap.get(oldAuthHolderId); - AuthenticationHolderEntity authHolder = authHolderRepository.getById(newAuthHolderId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setAuthenticationHolder(authHolder); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToAuthHolderRefs.clear(); - for (Long oldAccessTokenId : accessTokenToRefreshTokenRefs.keySet()) { - Long oldRefreshTokenId = accessTokenToRefreshTokenRefs.get(oldAccessTokenId); - Long newRefreshTokenId = refreshTokenOldToNewIdMap.get(oldRefreshTokenId); - OAuth2RefreshTokenEntity refreshToken = tokenRepository.getRefreshTokenById(newRefreshTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setRefreshToken(refreshToken); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToRefreshTokenRefs.clear(); - refreshTokenOldToNewIdMap.clear(); - for (Long oldAccessTokenId : accessTokenToIdTokenRefs.keySet()) { - Long oldIdTokenId = accessTokenToIdTokenRefs.get(oldAccessTokenId); - Long newIdTokenId = accessTokenOldToNewIdMap.get(oldIdTokenId); - OAuth2AccessTokenEntity idToken = tokenRepository.getAccessTokenById(newIdTokenId); - Long newAccessTokenId = accessTokenOldToNewIdMap.get(oldAccessTokenId); - OAuth2AccessTokenEntity accessToken = tokenRepository.getAccessTokenById(newAccessTokenId); - accessToken.setIdToken(idToken); - tokenRepository.saveAccessToken(accessToken); - } - accessTokenToIdTokenRefs.clear(); - for (Long oldGrantId : grantToAccessTokensRefs.keySet()) { - Set oldAccessTokenIds = grantToAccessTokensRefs.get(oldGrantId); - Set tokens = new HashSet(); - for(Long oldTokenId : oldAccessTokenIds) { - Long newTokenId = accessTokenOldToNewIdMap.get(oldTokenId); - tokens.add(tokenRepository.getAccessTokenById(newTokenId)); - } - Long newGrantId = grantOldToNewIdMap.get(oldGrantId); - ApprovedSite site = approvedSiteRepository.getById(newGrantId); - site.setApprovedAccessTokens(tokens); - approvedSiteRepository.save(site); - } - accessTokenOldToNewIdMap.clear(); - grantOldToNewIdMap.clear(); - for (Long permissionId : permissionToResourceRefs.keySet()) { - Long oldResourceId = permissionToResourceRefs.get(permissionId); - Long newResourceId = resourceSetOldToNewIdMap.get(oldResourceId); - Permission p = permissionRepository.getById(permissionId); - ResourceSet rs = resourceSetRepository.getById(newResourceId); - p.setResourceSet(rs); - permissionRepository.saveRawPermission(p); - logger.debug("Mapping rsid " + oldResourceId + " to " + newResourceId + " for permission " + permissionId); - } - permissionToResourceRefs.clear(); - resourceSetOldToNewIdMap.clear(); - - logger.info("Done fixing object references."); - } - -} diff --git a/uma-server-webapp/src/main/resources/db/clients.sql b/uma-server-webapp/src/main/resources/db/hsql/clients.sql old mode 100644 new mode 100755 similarity index 99% rename from uma-server-webapp/src/main/resources/db/clients.sql rename to uma-server-webapp/src/main/resources/db/hsql/clients.sql index cb8a6c2367..8d41bcad94 --- a/uma-server-webapp/src/main/resources/db/clients.sql +++ b/uma-server-webapp/src/main/resources/db/hsql/clients.sql @@ -28,7 +28,7 @@ INSERT INTO client_scope_TEMP (owner_id, scope) VALUES 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'), diff --git a/uma-server-webapp/src/main/resources/db/hsql/scopes.sql b/uma-server-webapp/src/main/resources/db/hsql/scopes.sql new file mode 100755 index 0000000000..c3ea0b1133 --- /dev/null +++ b/uma-server-webapp/src/main/resources/db/hsql/scopes.sql @@ -0,0 +1,35 @@ +-- +-- 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), + ('uma_protection', 'manage protected resources', 'briefcase', false, false), + ('uma_authorization', 'request access to protected resources', 'share', 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; diff --git a/uma-server-webapp/src/main/resources/db/mysql/clients.sql b/uma-server-webapp/src/main/resources/db/mysql/clients.sql new file mode 100755 index 0000000000..02444c4732 --- /dev/null +++ b/uma-server-webapp/src/main/resources/db/mysql/clients.sql @@ -0,0 +1,69 @@ +-- +-- 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), + ('rs', 'secret', 'Test UMA RS', false, null, null, 600, false), + ('c', 'secret', 'Test UMA Client', false, null, null, 600, false); + +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES + ('client', 'openid'), + ('client', 'profile'), + ('client', 'email'), + ('client', 'address'), + ('client', 'phone'), + ('client', 'offline_access'), + ('rs', 'uma_protection'), + ('c', 'uma_authorization'); + +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'), + ('rs', 'authorization_code'), + ('rs', 'implicit'), + ('c', 'authorization_code'), + ('c', 'implicit'); + +-- +-- 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/uma-server-webapp/src/main/resources/db/scopes.sql b/uma-server-webapp/src/main/resources/db/mysql/scopes.sql old mode 100644 new mode 100755 similarity index 64% rename from uma-server-webapp/src/main/resources/db/scopes.sql rename to uma-server-webapp/src/main/resources/db/mysql/scopes.sql index e0345dce04..bdcc0f6e30 --- a/uma-server-webapp/src/main/resources/db/scopes.sql +++ b/uma-server-webapp/src/main/resources/db/mysql/scopes.sql @@ -2,7 +2,7 @@ -- Turn off autocommit and start a transaction so that we can use the temp tables -- -SET AUTOCOMMIT FALSE; +SET AUTOCOMMIT = 0; START TRANSACTION; @@ -24,12 +24,10 @@ INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_sco -- 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, structured, structured_param_description FROM system_scope_TEMP) AS vals(scope, description, icon, restricted, default_scope, structured, structured_param_description) - ON vals.scope = system_scope.scope - WHEN NOT MATCHED THEN - INSERT (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES(vals.scope, vals.description, vals.icon, vals.restricted, vals.default_scope, vals.structured, vals.structured_param_description); +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 TRUE; \ No newline at end of file +SET AUTOCOMMIT = 1; diff --git a/uma-server-webapp/src/main/resources/db/oracle/clients_oracle.sql b/uma-server-webapp/src/main/resources/db/oracle/clients_oracle.sql new file mode 100755 index 0000000000..783ff2d3a4 --- /dev/null +++ b/uma-server-webapp/src/main/resources/db/oracle/clients_oracle.sql @@ -0,0 +1,61 @@ +-- +-- 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_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 + ('rs', 'secret', 'Test UMA RS', false, null, null, 600, false); +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 + ('c', 'secret', 'Test UMA Client', false, null, null, 600, false); + +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_scope_TEMP (owner_id, scope) VALUES ('rs', 'uma_protection'); +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES ('c', 'uma_authorization'); + +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'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('rs', 'authorization_code'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('rs', 'implicit'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('c', 'authorization_code'); +INSERT INTO client_grant_type_TEMP (owner_id, grant_type) VALUES ('c', 'implicit'); + +-- +-- 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/uma-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql b/uma-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql new file mode 100755 index 0000000000..a52e021dea --- /dev/null +++ b/uma-server-webapp/src/main/resources/db/oracle/scopes_oracle.sql @@ -0,0 +1,31 @@ +-- +-- Insert scope information into the temporary tables. +-- + +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('openid', 'log in using your identity', 'user', 0, 1, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('profile', 'basic profile information', 'list-alt', 0, 1, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('email', 'email address', 'envelope', 0, 1, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('address', 'physical address', 'home', 0, 1, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('phone', 'telephone number', 'bell', 0, 1, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('offline_access', 'offline access', 'time', 0, 0, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('uma_protection', 'manage protected resources', 'briefcase', 0, 0, 0, null); +INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES + ('uma_authorization', 'request access to protected resources', 'share', 0, 0, 0, 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, restricted, default_scope, structured, structured_param_description FROM system_scope_TEMP) vals + ON (vals.scope = system_scope.scope) + WHEN NOT MATCHED THEN + INSERT (id, scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES(system_scope_seq.nextval, vals.scope, + vals.description, vals.icon, vals.restricted, vals.default_scope, vals.structured, vals.structured_param_description); diff --git a/uma-server-webapp/src/main/resources/db/psql/clients.sql b/uma-server-webapp/src/main/resources/db/psql/clients.sql new file mode 100755 index 0000000000..d4c75e7fe6 --- /dev/null +++ b/uma-server-webapp/src/main/resources/db/psql/clients.sql @@ -0,0 +1,74 @@ +-- +-- 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), + ('rs', 'secret', 'Test UMA RS', false, null, null, 600, false), + ('c', 'secret', 'Test UMA Client', false, null, null, 600, false); + +INSERT INTO client_scope_TEMP (owner_id, scope) VALUES + ('client', 'openid'), + ('client', 'profile'), + ('client', 'email'), + ('client', 'address'), + ('client', 'phone'), + ('client', 'offline_access'), + ('rs', 'uma_protection'), + ('c', 'uma_authorization'); + +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'), + ('rs', 'authorization_code'), + ('rs', 'implicit'), + ('c', 'authorization_code'), + ('c', 'implicit'); + +-- +-- 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/scopes.sql b/uma-server-webapp/src/main/resources/db/psql/scopes.sql old mode 100644 new mode 100755 similarity index 60% rename from openid-connect-server-webapp/src/main/resources/db/scopes.sql rename to uma-server-webapp/src/main/resources/db/psql/scopes.sql index 27792880fc..8b2611b832 --- a/openid-connect-server-webapp/src/main/resources/db/scopes.sql +++ b/uma-server-webapp/src/main/resources/db/psql/scopes.sql @@ -2,7 +2,7 @@ -- Turn off autocommit and start a transaction so that we can use the temp tables -- -SET AUTOCOMMIT FALSE; +--SET AUTOCOMMIT = OFF; START TRANSACTION; @@ -22,12 +22,12 @@ INSERT INTO system_scope_TEMP (scope, description, icon, restricted, default_sco -- 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, structured, structured_param_description FROM system_scope_TEMP) AS vals(scope, description, icon, restricted, default_scope, structured, structured_param_description) - ON vals.scope = system_scope.scope - WHEN NOT MATCHED THEN - INSERT (scope, description, icon, restricted, default_scope, structured, structured_param_description) VALUES(vals.scope, vals.description, vals.icon, vals.restricted, vals.default_scope, vals.structured, vals.structured_param_description); +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 CONFLICT(scope) + DO NOTHING; COMMIT; -SET AUTOCOMMIT TRUE; \ No newline at end of file +--SET AUTOCOMMIT = ON; + diff --git a/uma-server-webapp/src/main/webapp/WEB-INF/application-context.xml b/uma-server-webapp/src/main/webapp/WEB-INF/application-context.xml deleted file mode 100644 index 8e5a73405b..0000000000 --- a/uma-server-webapp/src/main/webapp/WEB-INF/application-context.xml +++ /dev/null @@ -1,268 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /introspect - /revoke - /token - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/uma-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml b/uma-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml new file mode 100644 index 0000000000..7c645d23a8 --- /dev/null +++ b/uma-server-webapp/src/main/webapp/WEB-INF/endpoint-config.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uma-server-webapp/src/main/webapp/WEB-INF/server-config.xml b/uma-server-webapp/src/main/webapp/WEB-INF/server-config.xml index added56d98..92685552c6 100644 --- a/uma-server-webapp/src/main/webapp/WEB-INF/server-config.xml +++ b/uma-server-webapp/src/main/webapp/WEB-INF/server-config.xml @@ -1,20 +1,19 @@ + 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. + --> + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> @@ -43,12 +42,27 @@ - + + + + + uma + messages + + + + + + + + + diff --git a/uma-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag b/uma-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag deleted file mode 100644 index d5888ef7c1..0000000000 --- a/uma-server-webapp/src/main/webapp/WEB-INF/tags/footer.tag +++ /dev/null @@ -1,38 +0,0 @@ -<%@ 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"%> -
    - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/uma-server-webapp/src/main/webapp/WEB-INF/ui-config.xml b/uma-server-webapp/src/main/webapp/WEB-INF/ui-config.xml new file mode 100644 index 0000000000..2cd7bfc33b --- /dev/null +++ b/uma-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 + resources/js/policy.js + + + + + + diff --git a/uma-server-webapp/src/main/webapp/WEB-INF/user-context.xml b/uma-server-webapp/src/main/webapp/WEB-INF/user-context.xml index f6103eecb8..4a2f7bb0d3 100644 --- a/uma-server-webapp/src/main/webapp/WEB-INF/user-context.xml +++ b/uma-server-webapp/src/main/webapp/WEB-INF/user-context.xml @@ -1,20 +1,19 @@ + 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. + --> + http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd + http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.2.xsd + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd + http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd + http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> @@ -47,7 +46,7 @@ - + @@ -110,7 +109,7 @@ - + @@ -119,18 +118,25 @@ - + - - + + + + + - + + + + + diff --git a/uma-server-webapp/src/main/webapp/resources/js/admin.js b/uma-server-webapp/src/main/webapp/resources/js/admin.js deleted file mode 100644 index f6cc186f6a..0000000000 --- a/uma-server-webapp/src/main/webapp/resources/js/admin.js +++ /dev/null @@ -1,1246 +0,0 @@ -/******************************************************************************* - * Copyright 2015 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. - *******************************************************************************/ - -Backbone.Model.prototype.fetchIfNeeded = function(options) { - var _self = this; - if (!options) { - options = {}; - } - var success = options.success; - options.success = function(c, r) { - _self.isFetched = true; - if (success) { - success(c, r); - } - }; - if (!this.isFetched) { - return this.fetch(options); - } else { - return options.success(this, null); - } -}; - -Backbone.Collection.prototype.fetchIfNeeded = function(options) { - var _self = this; - if (!options) { - options = {}; - } - var success = options.success; - options.success = function(c, r) { - _self.isFetched = true; - if (success) { - success(c, r); - } - }; - if (!this.isFetched) { - return this.fetch(options); - } else { - return options.success(this, null); - } -}; - -var URIModel = Backbone.Model.extend({ - - validate: function(attrs){ - - var expression = /^(?:([a-z0-9+.-]+:\/\/)((?:(?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(:(?:\d*))?(\/(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?|([a-z0-9+.-]+:)(\/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?)(\?(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?(#(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?$/i; - var regex = new RegExp(expression); - - if (attrs.item == null || !attrs.item.match(regex)) { - return "Invalid URI"; - } - } - -}); - - -/* -* Backbone JS Reusable ListWidget -* Options -* { -* collection: Backbone JS Collection -* type: ('uri'|'default') -* autocomplete: ['item1','item2'] List of auto complete items -* } -* - */ -var ListWidgetChildView = Backbone.View.extend({ - - tagName: 'tr', - - events:{ - "click .btn-delete-list-item":'deleteItem', - "change .checkbox-list-item":'toggleCheckbox' - }, - - deleteItem:function (e) { - e.preventDefault(); - e.stopImmediatePropagation(); - //this.$el.tooltip('delete'); - - this.model.destroy({ - error:function (error, response) { - console.log("An error occurred when deleting from a list widget"); - - //Pull out the response text. - var responseJson = JSON.parse(response.responseText); - - //Display an alert with an error message - $('#modalAlert div.modal-header').html(responseJson.error); - $('#modalAlert div.modal-body').html(responseJson.error_description); - - $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog - "backdrop" : "static", - "keyboard" : true, - "show" : true // ensure the modal is shown immediately - }); - } - }); - - }, - - toggleCheckbox:function(e) { - e.preventDefault(); - e.stopImmediatePropagation(); - if ($(e.target).is(':checked')) { - this.options.collection.add(this.model); - } else { - this.options.collection.remove(this.model); - } - - }, - - initialize:function (options) { - this.options = {toggle: false, checked: false}; - _.extend(this.options, options); - if (!this.template) { - this.template = _.template($('#tmpl-list-widget-child').html()); - } - }, - - render:function () { - - var data = {model: this.model.toJSON(), opt: this.options}; - - this.$el.html(this.template(data)); - - $('.item-full', this.el).hide(); - - if (this.model.get('item').length > 30) { - this.$el.tooltip({title:$.t('admin.list-widget.tooltip')}); - - var _self = this; - - $(this.el).click(function(event) { - event.preventDefault(); - $('.item-short', _self.el).hide(); - $('.item-full', _self.el).show(); - _self.$el.tooltip('destroy'); - }); - } - - - - $(this.el).i18n(); - return this; - } -}); - -var ListWidgetView = Backbone.View.extend({ - - tagName: "div", - - childView:ListWidgetChildView, - - events:{ - "click .btn-add-list-item":"addItem", - "keypress":function (e) { - // trap the enter key - if (e.which == 13) { - e.preventDefault(); - this.addItem(e); - $("input", this.$el).focus(); - } - } - }, - - initialize:function (options) { - this.options = options; - - if (!this.template) { - this.template = _.template($('#tmpl-list-widget').html()); - } - - this.collection.bind('add', this.render, this); - this.collection.bind('remove', this.render, this); - }, - - addItem:function(e) { - e.preventDefault(); - - var input_value = $("input", this.el).val().trim(); - - if (input_value === ""){ - return; - } - - var model; - - if (this.options.type == 'uri') { - model = new URIModel({item:input_value}); - } else { - model = new Backbone.Model({item:input_value}); - model.validate = function(attrs) { - if(!attrs.item) { - return "value can't be null"; - } - }; - } - - // if it's valid and doesn't already exist - if (model.get("item") != null && this.collection.where({item: input_value}).length < 1) { - this.collection.add(model); - } else { - // else add a visual error indicator - $(".control-group", this.el).addClass('error'); - } - }, - - render:function (eventName) { - - this.$el.html(this.template({placeholder:this.options.placeholder, - helpBlockText:this.options.helpBlockText})); - - _self = this; - - if (_.size(this.collection.models) == 0 && _.size(this.options.autocomplete) == 0) { - $("tbody", _self.el).html($('#tmpl-list-widget-child-empty').html()); - } else { - - // make a copy of our collection to work from - var values = this.collection.clone(); - - // look through our autocomplete values (if we have them) and render them all as checkboxes - if (this.options.autocomplete) { - _.each(this.options.autocomplete, function(option) { - var found = _.find(values.models, function(element) { - return element.get('item') == option; - }); - - var model = null; - var checked = false; - - if (found) { - // if we found the element, check the box - model = found; - checked = true; - // and remove it from the list of items to be rendered later - values.remove(found, {silent: true}); - } else { - model = new Backbone.Model({item:option}); - checked = false; - } - - var el = new this.childView({model:model, toggle: true, checked: checked, collection: _self.collection}).render().el; - $("tbody", _self.el).append(el); - - }, this); - } - - - // now render everything not in the autocomplete list - _.each(values.models, function (model) { - - var el = new this.childView({model:model, collection: _self.collection}).render().el; - $("tbody", _self.el).append(el); - }, this); - } - - $(this.el).i18n(); - return this; - } - -}); - -var BlackListModel = Backbone.Model.extend({ - idAttribute: 'id', - - urlRoot: 'api/blacklist' -}); - -var BlackListCollection = Backbone.Collection.extend({ - initialize: function() { }, - - url: "api/blacklist" -}); - -var BreadCrumbView = Backbone.View.extend({ - - tagName: 'ul', - - initialize:function (options) { - this.options = options; - - if (!this.template) { - this.template = _.template($('#tmpl-breadcrumbs').html()); - } - - this.$el.addClass('breadcrumb'); - - this.collection.bind('add', this.render, this); - }, - - render:function () { - - this.$el.empty(); - var parent = this; - - // go through each of the breadcrumb models - _.each(this.collection.models, function (crumb, index) { - - // if it's the last index in the crumbs then render the link inactive - if (index == parent.collection.size() - 1) { - crumb.set({active:true}, {silent:true}); - } else { - crumb.set({active:false}, {silent:true}); - } - - this.$el.append(this.template(crumb.toJSON())); - }, this); - - $('#breadcrumbs').html(this.el); - $(this.el).i18n(); - } -}); - - -var BlackListListView = Backbone.View.extend({ - tagName: 'span', - - initialize:function(options) { - this.options = options; - if (!this.template) { - this.template = _.template($('#tmpl-blacklist-form').html()); - } - }, - - load:function(callback) { - if (this.model.isFetched) { - callback(); - return; - } - - $('#loadingbox').sheet('show'); - $('#loading').html( - '' + $.t('admin.blacklist') + ' ' - ); - - $.when(this.model.fetchIfNeeded()).done(function() { - $('#loading-blacklist').addClass('label-success'); - $('#loadingbox').sheet('hide'); - callback(); - }); - }, - - events: { - "click .refresh-table":"refreshTable" - }, - - refreshTable:function(e) { - e.preventDefault(); - var _self = this; - $('#loadingbox').sheet('show'); - $('#loading').html( - '' + $.t('admin.blacklist') + ' ' - ); - - $.when(this.model.fetch()).done(function() { - $('#loadingbox').sheet('hide'); - _self.render(); - }); - }, - - render:function (eventName) { - - $(this.el).html(this.template(this.model.toJSON())); - - $('#blacklist .controls', this.el).html(new BlackListWidgetView({ - type: 'uri', - placeholder: 'http://', - collection: this.model - }).render().el); - - $(this.el).i18n(); - return this; - } -}); - -var BlackListWidgetView = ListWidgetView.extend({ - - childView: ListWidgetChildView.extend({ - render:function(options) { - this.options = options; - var uri = this.model.get('uri'); - - this.$el.html(this.template({item: uri})); - - if (uri.length > 30) { - this.$el.tooltip({title:uri}); - } - return this; - - } - }), - - addItem:function(e) { - e.preventDefault(); - - var input_value = $("input", this.el).val().trim(); - - if (input_value === "") { - return; - } - - // TODO: URI/pattern validation, check against existing clients - - var item = new BlackListModel({ - uri: input_value - }); - - var _self = this; // closures... - - item.save({}, { - success:function() { - _self.collection.add(item); - }, - error:function(error, response) { - //Pull out the response text. - var responseJson = JSON.parse(response.responseText); - - //Display an alert with an error message - $('#modalAlert div.modal-header').html(responseJson.error); - $('#modalAlert div.modal-body').html(responseJson.error_description); - - $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog - "backdrop" : "static", - "keyboard" : true, - "show" : true // ensure the modal is shown immediately - }); - } - }); - - } - -}); - -// Stats table - -var StatsModel = Backbone.Model.extend({ - url: "api/stats/byclientid" -}); - -// User Profile - -var UserProfileView = Backbone.View.extend({ - tagName: 'span', - - initialize:function(options) { - this.options = options; - if (!this.template) { - this.template = _.template($('#tmpl-user-profile-element').html()); - } - }, - - render:function() { - - $(this.el).html($('#tmpl-user-profile').html()); - - _.each(this.model, function (value, key) { - if (key && value) { - $('dl', this.el).append( - this.template({key: key, value: value}) - ); - } - }, this); - - $(this.el).i18n(); - return this; - } -}); - -// Router -var AppRouter = Backbone.Router.extend({ - - routes:{ - "admin/clients":"listClients", - "admin/client/new":"newClient", - "admin/client/:id":"editClient", - - "admin/whitelists":"whiteList", - "admin/whitelist/new/:cid":"newWhitelist", - "admin/whitelist/:id":"editWhitelist", - - "admin/blacklist":"blackList", - - "admin/scope":"siteScope", - "admin/scope/new":"newScope", - "admin/scope/:id":"editScope", - - "user/approved":"approvedSites", - "user/tokens":"tokens", - "user/profile":"profile", - - "user/policy":"policy", - "user/policy/:rsid":"editPolicies", - "user/policy/:rsid/new":"newPolicy", - "user/policy/:rsid/:pid":"editPolicy", - - "dev/dynreg":"dynReg", - "dev/dynreg/new":"newDynReg", - "dev/dynreg/edit":"editDynReg", - - "dev/resource":"resReg", - "dev/resource/new":"newResReg", - "dev/resource/edit":"editResReg", - - "": "root" - - }, - - root:function() { - if (isAdmin()) { - this.navigate('admin/clients', {trigger: true}); - } else { - this.navigate('user/approved', {trigger: true}); - } - }, - - initialize:function () { - - this.clientList = new ClientCollection(); - this.whiteListList = new WhiteListCollection(); - this.blackListList = new BlackListCollection(); - this.approvedSiteList = new ApprovedSiteCollection(); - this.systemScopeList = new SystemScopeCollection(); - this.clientStats = new StatsModel(); - this.accessTokensList = new AccessTokenCollection(); - this.refreshTokensList = new RefreshTokenCollection(); - this.resourceSetList = new ResourceSetCollection(); - - this.breadCrumbView = new BreadCrumbView({ - collection:new Backbone.Collection() - }); - - this.breadCrumbView.render(); - - var base = $('base').attr('href'); - $.getJSON(base + '.well-known/openid-configuration', function(data) { - app.serverConfiguration = data; - var baseUrl = $.url(app.serverConfiguration.issuer); - Backbone.history.start({pushState: true, root: baseUrl.attr('relative') + 'manage/'}); - }); - - }, - - listClients:function () { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('client.manage'), href:"manage/#admin/clients"} - ]); - - this.updateSidebar('admin/clients'); - - var view = new ClientListView({model:this.clientList, stats: this.clientStats, systemScopeList: this.systemScopeList, whiteListList: this.whiteListList}); - - view.load(function() { - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('client.manage')); - }); - - }, - - newClient:function() { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('client.manage'), href:"manage/#admin/clients"}, - {text:$.t('client.client-form.new'), href:""} - ]); - - this.updateSidebar('admin/clients'); - - var client = new ClientModel(); - - var view = new ClientFormView({model:client, systemScopeList: this.systemScopeList}); - view.load(function() { - // set up this new client to require a secret and have us autogenerate one - var userInfo = getUserInfo(); - var contacts = []; - if (userInfo != null && userInfo.email != null) { - contacts.push(userInfo.email); - } - - client.set({ - tokenEndpointAuthMethod: "SECRET_BASIC", - generateClientSecret:true, - displayClientSecret:false, - requireAuthTime:true, - defaultMaxAge:60000, - scope: _.uniq(_.flatten(app.systemScopeList.defaultScopes().pluck("value"))), - accessTokenValiditySeconds:3600, - idTokenValiditySeconds:600, - grantTypes: ["authorization_code"], - responseTypes: ["code"], - subjectType: "PUBLIC", - contacts: contacts - }, { silent: true }); - - - $('#content').html(view.render().el); - setPageTitle($.t('client.client-form.new')); - }); - }, - - editClient:function(id) { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('client.manage'), href:"manage/#admin/clients"}, - {text:$.t('client.client-form.edit'), href:"manage/#admin/client/" + id} - ]); - - this.updateSidebar('admin/clients'); - - var client = this.clientList.get(id); - if (!client) { - client = new ClientModel({id:id}); - } - - var view = new ClientFormView({model:client, systemScopeList: app.systemScopeList}); - view.load(function() { - if ($.inArray("refresh_token", client.get("grantTypes")) != -1) { - client.set({ - allowRefresh: true - }, { silent: true }); - } - - client.set({ - generateClientSecret:false, - displayClientSecret:false - }, { silent: true }); - - $('#content').html(view.render().el); - setPageTitle($.t('client.client-form.edit')); - }); - - }, - - whiteList:function () { - - if (!isAdmin()) { - this.root(); - return; - } - - this.updateSidebar('admin/whitelists'); - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('whitelist.manage'), href:"manage/#admin/whitelists"} - ]); - - var view = new WhiteListListView({model:this.whiteListList, clientList: this.clientList, systemScopeList: this.systemScopeList}); - - view.load( - function() { - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('whitelist.manage')); - } - ); - - - }, - - newWhitelist:function(cid) { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('whitelist.manage'), href:"manage/#admin/whitelists"}, - {text:$.t('whitelist.new'), href:"manage/#admin/whitelist/new/" + cid} - ]); - - this.updateSidebar('admin/whitelists'); - - var whiteList = new WhiteListModel(); - - var client = this.clientList.get(cid); - if (!client) { - client = new ClientModel({id: cid}); - } - - var view = new WhiteListFormView({model: whiteList, client: client, systemScopeList: this.systemScopeList}); - - view.load( - function() { - - // set the scopes on the model now that everything's loaded - whiteList.set({allowedScopes: client.get('scope')}, {silent: true}); - - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('whitelist.manage')); - } - ); - - }, - - editWhitelist:function(id) { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('whitelist.manage'), href:"manage/#admin/whitelists"}, - {text:$.t('whitelist.edit'), href:"manage/#admin/whitelist/" + id} - ]); - - this.updateSidebar('admin/whitelists'); - - var whiteList = this.whiteListList.get(id); - if (!whiteList) { - whiteList = new WhiteListModel({id: id}); - } - - var view = new WhiteListFormView({model: whiteList, clientList: this.clientList, systemScopeList: this.systemScopeList}); - - view.load( - function() { - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('whitelist.manage')); - } - ); - - }, - - approvedSites:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('grant.manage-approved-sites'), href:"manage/#user/approve"} - ]); - - this.updateSidebar('user/approved'); - - var view = new ApprovedSiteListView({model:this.approvedSiteList, clientList: this.clientList, systemScopeList: this.systemScopeList}); - - view.load( - function(collection, response, options) { - $('#content').html(view.render().el); - setPageTitle($.t('grant.manage-approved-sites')); - } - ); - - }, - - tokens:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('token.manage'), href:"manage/#user/tokens"} - ]); - - this.updateSidebar('user/tokens'); - - var view = new TokenListView({model: {access: this.accessTokensList, refresh: this.refreshTokensList}, clientList: this.clientList, systemScopeList: this.systemScopeList}); - - view.load( - function(collection, response, options) { - $('#content').html(view.render().el); - setPageTitle($.t('token.manage')); - } - ); - - }, - - notImplemented:function(){ - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""} - ]); - - this.updateSidebar('none'); - - $('#content').html("

    Not implemented yet.

    "); - }, - - blackList:function() { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.manage-blacklist'), href:"manage/#admin/blacklist"} - ]); - - this.updateSidebar('admin/blacklist'); - - var view = new BlackListListView({model:this.blackListList}); - - view.load( - function(collection, response, options) { - $('#content').html(view.render().el); - setPageTitle($.t('admin.manage-blacklist')); - } - ); - }, - - siteScope:function() { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('scope.manage'), href:"manage/#admin/scope"} - ]); - - this.updateSidebar('admin/scope'); - - var view = new SystemScopeListView({model:this.systemScopeList}); - - view.load(function() { - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('scope.manage')); - }); - - }, - - newScope:function() { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('scope.manage'), href:"manage/#admin/scope"}, - {text:$.t('scope.system-scope-form.new'), href:"manage/#admin/scope/new"} - ]); - - this.updateSidebar('admin/scope'); - - var scope = new SystemScopeModel(); - - var view = new SystemScopeFormView({model:scope}); - view.load(function() { - $('#content').html(view.render().el); - setPageTitle($.t('scope.system-scope-form.new')); - }); - - }, - - editScope:function(sid) { - - if (!isAdmin()) { - this.root(); - return; - } - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('scope.manage'), href:"manage/#admin/scope"}, - {text:$.t('scope.system-scope-form.edit'), href:"manage/#admin/scope/" + sid} - ]); - - this.updateSidebar('admin/scope'); - - var scope = this.systemScopeList.get(sid); - if (!scope) { - scope = new SystemScopeModel({id: sid}); - } - - var view = new SystemScopeFormView({model:scope}); - view.load(function() { - $('#content').html(view.render().el); - setPageTitle($.t('scope.system-scope-form.new')); - }); - - }, - - dynReg:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.self-service-client'), href:"manage/#dev/dynreg"} - ]); - - var view = new DynRegRootView({systemScopeList: this.systemScopeList}); - - this.updateSidebar('dev/dynreg'); - - view.load(function() { - $('#content').html(view.render().el); - - setPageTitle($.t('admin.self-service-client')); - }); - - }, - - newDynReg:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.self-service-client'), href:"manage/#dev/dynreg"}, - {text:$.t('dynreg.new-client'), href:"manage/#dev/dynreg/new"} - ]); - - this.updateSidebar('dev/dynreg'); - - var client = new DynRegClient(); - var view = new DynRegEditView({model: client, systemScopeList:this.systemScopeList}); - - view.load(function() { - - var userInfo = getUserInfo(); - var contacts = []; - if (userInfo != null && userInfo.email != null) { - contacts.push(userInfo.email); - } - - client.set({ - require_auth_time:true, - default_max_age:60000, - scope: _.uniq(_.flatten(app.systemScopeList.defaultUnrestrictedScopes().pluck("value"))).join(" "), - token_endpoint_auth_method: 'client_secret_basic', - grant_types: ["authorization_code"], - response_types: ["code"], - subject_type: "public", - contacts: contacts - }, { silent: true }); - - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('dynreg.new-client')); - - }); - - }, - - editDynReg:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.self-service-client'), href:"manage/#dev/dynreg"}, - {text:$.t('dynreg.edit-existing'), href:"manage/#dev/dynreg/edit"} - ]); - - this.updateSidebar('dev/dynreg'); - - setPageTitle($.t('dynreg.edit-existing')); - // note that this doesn't actually load the client, that's supposed to happen elsewhere... - }, - - resReg:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.self-service-resource'), href:"manage/#dev/resource"} - ]); - - this.updateSidebar('dev/resource'); - - var view = new ResRegRootView({systemScopeList: this.systemScopeList}); - view.load(function() { - $('#content').html(view.render().el); - - setPageTitle($.t('admin.self-service-resource')); - }); - - }, - - newResReg:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.self-service-resource'), href:"manage/#dev/resource"}, - {text:$.t('rsreg.new'), href:"manage/#dev/resource/new"} - ]); - - this.updateSidebar('dev/resource'); - - var client = new ResRegClient(); - var view = new ResRegEditView({model: client, systemScopeList:this.systemScopeList}); - - view.load(function() { - - var userInfo = getUserInfo(); - var contacts = []; - if (userInfo != null && userInfo.email != null) { - contacts.push(userInfo.email); - } - - client.set({ - scope: _.uniq(_.flatten(app.systemScopeList.defaultUnrestrictedScopes().pluck("value"))).join(" "), - token_endpoint_auth_method: 'client_secret_basic', - contacts: contacts - }, { silent: true }); - - $('#content').html(view.render().el); - view.delegateEvents(); - setPageTitle($.t('rsreg.new')); - - }); - - }, - - editResReg:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.self-service-resource'), href:"manage/#dev/resource"}, - {text:$.t('rsreg.edit'), href:"manage/#dev/resource/edit"} - ]); - - this.updateSidebar('dev/resource'); - - setPageTitle($.t('rsreg.edit')); - // note that this doesn't actually load the client, that's supposed to happen elsewhere... - }, - - profile:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('admin.user-profile.show'), href:"manage/#user/profile"} - ]); - - this.updateSidebar('user/profile'); - - var view = new UserProfileView({model: getUserInfo()}); - $('#content').html(view.render().el); - - setPageTitle($.t('admin.user-profile.show')); - - }, - - policy:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('policy.resource-sets'), href:"manage/#user/policy"} - ]); - - this.updateSidebar('user/policy'); - - var view = new ResourceSetListView({model: this.resourceSetList, clientList: this.clientList, systemScopeList: this.systemScopeList}); - - view.load(function() { - $('#content').html(view.render().el); - setPageTitle($.t('policy.resource-sets')); - }); - - }, - - editPolicies:function(rsid) { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('policy.resource-sets'), href:"manage/#user/policy"}, - {text:$.t('policy.edit-policies'), href:"manage/#user/policy/" + rsid} - ]); - - this.updateSidebar('user/policy'); - - var rs = this.resourceSetList.get(rsid); - var policies = null; - if (rs == null) { - // need to load it directly - policies = new PolicyCollection([], {rsid: rsid}); - rs = new ResourceSetModel({id: rsid}); - this.resourceSetList.add(rs); // it will be loaded below, don't need to load it again in the future - } else { - // the resource set is loaded, preload the claims - policies = new PolicyCollection(rs.get('policies'), {rsid: rsid}); - policies.isFetched = true; - } - - var view = new PolicyListView({model: policies, rs: rs, systemScopeList: this.systemScopeList}); - - view.load(function() { - $('#content').html(view.render().el); - setPageTitle($.t('policy.edit-policy')); - }); - - }, - - newPolicy:function(rsid) { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('policy.resource-sets'), href:"manage/#user/policy"}, - {text:$.t('policy.edit-policies'), href:"manage/#user/policy/" + rsid}, - {text:$.t('policy.new-policy'), href:"manage/#user/policy/" + rsid + "/new"} - ]); - - this.updateSidebar('user/policy'); - - var policy = policy = new PolicyModel({}, {rsid: rsid}); - - var rs = this.resourceSetList.get(rsid); - if (rs == null) { - // need to load it directly - rs = new ResourceSetModel({id: rsid}); - this.resourceSetList.add(rs); // it will be loaded below, don't need to load it again in the future - } - - var view = new PolicyFormView({model: policy, rs: rs, systemScopeList: this.systemScopeList}); - - view.load(function() { - $('#content').html(view.render().el); - setPageTitle($.t('policy.edit-policy')); - }); - }, - - editPolicy:function(rsid, pid) { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:$.t('admin.home'), href:""}, - {text:$.t('policy.resource-sets'), href:"manage/#user/policy"}, - {text:$.t('policy.edit-policies'), href:"manage/#user/policy/" + rsid}, - {text:$.t('policy.edit-policy'), href:"manage/#user/policy/" + rsid + "/" + pid} - ]); - - this.updateSidebar('user/policy'); - - var rs = this.resourceSetList.get(rsid); - var policy = null; - if (rs == null) { - // need to load it directly - policy = new PolicyModel({id: pid}, {rsid: rsid}); - rs = new ResourceSetModel({id: rsid}); - this.resourceSetList.add(rs); // it will be loaded below, don't need to load it again in the future - } else { - // the resource set is loaded, preload the claims - _.each(rs.get('policies'), function(p) { - if (p.id == pid) { - policy = new PolicyModel(p, {rsid: rsid}); - policy.isFetched = true; - } - }); - if (policy == null) { - // need to load it directly - policy = new PolicyModel({id: pid}, {rsid: rsid}); - } - } - - var view = new PolicyFormView({model: policy, rs: rs, systemScopeList: this.systemScopeList}); - - view.load(function() { - $('#content').html(view.render().el); - setPageTitle($.t('policy.edit-policy')); - }); - - - }, - - updateSidebar:function(item) { - $('.sidebar-nav li.active').removeClass('active'); - - $('.sidebar-nav li a[href^="manage/#' + item + '"]').parent().addClass('active'); - } -}); - -// holds the global app. -// this gets init after the templates load -var app = null; - -// main -$(function () { - - var _load = function (templates) { - $('body').append(templates); - }; - - // load templates and append them to the body - $.when( - $.get('resources/template/admin.html', _load), - $.get('resources/template/client.html', _load), - $.get('resources/template/grant.html', _load), - $.get('resources/template/scope.html', _load), - $.get('resources/template/whitelist.html', _load), - $.get('resources/template/dynreg.html', _load), - $.get('resources/template/rsreg.html', _load), - $.get('resources/template/token.html', _load), - $.get('resources/template/policy.html', _load) - ).done(function() { - $.ajaxSetup({cache:false}); - app = new AppRouter(); - - // grab all hashed URLs and send them through the app router instead - $(document).on('click', 'a[href^="manage/#"]', function(event) { - event.preventDefault(); - app.navigate(this.hash.slice(1), {trigger: true}); - }); - }); - -}); - - diff --git a/uma-server-webapp/src/main/webapp/resources/js/locale/en/messages.json b/uma-server-webapp/src/main/webapp/resources/js/locale/en/messages.json deleted file mode 100644 index c9e7d31c0b..0000000000 --- a/uma-server-webapp/src/main/webapp/resources/js/locale/en/messages.json +++ /dev/null @@ -1,494 +0,0 @@ -{ - "admin": { - "blacklist": "Blacklist", - "blacklist-form": { - "blacklisted-uris": "Blacklisted URIs" - }, - "home": "Home", - "list-widget": { - "empty": "There are no items in this list.", - "tooltip": "Click to display full value." - }, - "manage-blacklist": "Manage Blacklisted Clients", - "self-service-client": "Self-service Client Registration", - "self-service-resource": "Self-service Protected Resource Registration", - "user-profile": { - "claim": "Claim name:", - "show": "View User Profile", - "text": "Your user profile has the following information:", - "value": "Claim value:" - }, - "policies": "Manage Protected Resource Policies" - }, - "client": { - "client-form": { - "access": "Access", - "access-token-no-timeout": "Access tokens do not time out", - "access-token-timeout": "Access Token Timeout", - "access-token-timeout-help": "Enter this time in seconds, minutes, or hours.", - "acr-values": "Default ACR Values", - "acr-values-placeholder": "new ACR value", - "acr-values-help": "Default Authentication Context Reference to request for this client", - "allow-introspection": "Allow calls to the Introspection Endpoint?", - "authentication-method": "Token Endpoint Authentication Method", - "authorization-code": "authorization code", - "client-credentials": "client credentials", - "client-description": "Description", - "client-description-help": "Human-readable text description", - "client-description-placeholder": "Type a description", - "client-id": "Client ID", - "client-id-help": "Unique identifier. If you leave this blank it will be automatically generated.", - "client-id-placeholder": "Type something", - "client-name": "Client name", - "client-name-help": "Human-readable application name", - "client-name-placeholder": "Type something", - "client-secret": "Client Secret", - "client-secret-placeholder": "Type a secret", - "contacts": "Contacts", - "contacts-help": "List of contacts for administrators of this client.", - "contacts-placeholder": "new contact", - "credentials": "Credentials", - "crypto": { - "a128cbc-hs256": "Composite Authenticated Encryption algorithm using AES in Cipher Block Chaining (CBC) mode with PKCS #5 padding with an integrity calculation using HMAC SHA-256, using a 256 bit CMK (and 128 bit CEK)", - "a256cbc-hs512": "Composite Authenticated Encryption algorithm using AES in CBC mode with PKCS #5 padding with an integrity calculation using HMAC SHA-512, using a 512 bit CMK (and 256 bit CEK)", - "a128gcm": "AES GCM using 128 bit keys", - "a256gcm": "AES GCM using 256 bit keys", - "a128kw": "AES Key Wrap Algorithm using 128 bit keys", - "a256kw": "AES Key Wrap Algorithm using 256 bit keys", - "default": "Use server default", - "dir": "Direct use of a shared symmetric key as the Content Master Key (CMK) for the block encryption step", - "ecdh-es": "Elliptic Curve Diffie-Hellman Ephemeral Static key agreement using the Concat KDF, with the agreed-upon key being used directly as the Content Master Key", - "ecdh-es-a128kw": "Elliptic Curve Diffie-Hellman Ephemeral Static key agreement per ECDH-ES and Section 4.7, but where the agreed-upon key is used to wrap the Content Master Key (CMK) with the A128KW function", - "ecdh-es-a256kw": "Elliptic Curve Diffie-Hellman Ephemeral Static key agreement per ECDH-ES and Section 4.7, but where the agreed-upon key is used to wrap the Content Master Key (CMK) with the A256KW function", - "none": "No encryption", - "rsa-oaep": "RSAES using Optimal Asymmetric Encryption Padding (OAEP)", - "rsa1-5": "RSAES-PKCS1-V1_5" - }, - "cryptography": "Crypto", - "display-secret": "Display/edit client secret:", - "edit": "Edit Client", - "generate-new-secret": "Generate a new client secret?", - "generate-new-secret-help": "New secret will be generated when you click 'Save'", - "generate-on-save": "Generate on Save", - "grant-types": "Grant Types", - "home": "Home Page", - "home-help": "URL for the client's home page, will be displayed to the user", - "hours": "hours", - "id": "ID:", - "id-token-crypto-algorithm": "ID Token Encryption Algorithm", - "id-token-crypto-method": "ID Token Encryption Method", - "id-token-signing-algorithm": "ID Token Signing Algorithm", - "id-token-timeout": "ID Token Timeout", - "implicit": "implicit", - "initiate-login": "Initiate Login", - "initiate-login-help": "URL to initiate login on the client", - "introspection": "Introspection", - "jwk-set": "JWK Set", - "jwk-set-help": "URL for the client's JSON Web Key set", - "logo": "Logo", - "logo-help": "URL that points to a logo image, will be displayed on approval page", - "main": "Main", - "max-age": "Default Max Age", - "max-age-help": "Default maximum session age before re-prompting", - "minutes": "minutes", - "new": "New Client", - "other": "Other", - "pairwise": "Pairwise", - "password": "password", - "policy": "Policy Statement", - "policy-help": "URL for the Policy Statement of this client, will be displayed to the user", - "post-logout": "Post-Logout Redirect", - "post-logout-help": "URL to redirect the client to after a logout operation", - "public": "Public", - "redelegation": "redelegation", - "redirect-uris": "Redirect URI(s)", - "redirect-uris-help": "URIs that the client can be redirected to after the authorization page", - "refresh": "refresh", - "refresh-tokens": "Refresh Tokens", - "refresh-tokens-issued": "Refresh tokens are issued for this client", - "refresh-tokens-reused": "Refresh tokens for this client are re-used", - "refresh-tokens-no-expire": "Refresh tokens do not time out", - "registered": "Registered at", - "registration-token": "Registration Token:", - "registration-access-token": "Registration Access Token", - "registration-token-error": "There was a problem loading the registration access token for this client.", - "request-object-signing-algorithm": "Request Object Signing Algorithm", - "request-uri": "Request URIs", - "request-uri-help": "URIs containing request objects used by this client", - "require-auth-time": "Require Authentication Time", - "require-auth-time-label": "Always require that the auth_time claim be sent in the id token", - "response-types": "Response Types", - "rotate-registration-token": "Rotate registration token", - "rotate-registration-token-confirm": "Are you sure you want to rotate this client's registration token?", - "rotate-registration-token-error": "There was a problem rotating the registration access token for this client.", - "saved": { - "no-secret": "No client secret", - "saved": "Client Saved", - "secret": "Secret:", - "show-secret": "Show Secret", - "unchanged": "unchanged" - }, - "scope-placeholder": "new scope", - "scope-help": "OAuth scopes this client is allowed to request", - "seconds": "seconds", - "secret-asymmetric-jwt": "Asymmetrically-signed JWT assertion", - "secret-http": "Client Secret over HTTP Basic", - "secret-none": "No authentication", - "secret-post": "Client Secret over HTTP POST", - "secret-symmetric-jwt": "Client Secret via symmetrically-signed JWT assertion", - "sector-identifier": "Sector Identifier URI", - "signing": { - "any": "Any allowed", - "default": "Use server default", - "ecdsa-256": "ECDSA using P-256 curve and SHA-256 hash algorithm", - "ecdsa-384": "ECDSA using P-384 curve and SHA-384 hash algorithm", - "ecdsa-512": "ECDSA using P-512 curve and SHA-512 hash algorithm", - "hmac-256": "HMAC using SHA-256 hash algorithm", - "hmac-384": "HMAC using SHA-384 hash algorithm", - "hmac-512": "HMAC using SHA-512 hash algorithm", - "none": "No digital signature", - "rsassa-256": "RSASSA using SHA-256 hash algorithm", - "rsassa-384": "RSASSA using SHA-384 hash algorithm", - "rsassa-512": "RSASSA using SHA-512 hash algorithm" - }, - "subject-type": "Subject Type", - "terms": "Terms of Service", - "terms-help": "URL for the Terms of Service of this client, will be displayed to the user", - "token-signing-algorithm": "Token Endpoint Authentication Signing Algorithm", - "tokens": "Tokens", - "type": "Application Type", - "type-native": "Native", - "type-web": "Web", - "unknown": "(Unknown)", - "user-info-crypto-algorithm": "User Info Endpoint Encryption Algorithm", - "user-info-crypto-method": "User Info Endpoint Encryption Method", - "user-info-signing-algorithm": "User Info Endpoint Signing Algorithm" - }, - "client-table": { - "allow-introspection-tooltip": "This client can perform token introspection", - "confirm": "Are you sure sure you would like to delete this client?", - "dynamically-registered-tooltip": "This client was dynamically registered. Click to view registration access token", - "match": { - "contacts": "contacts", - "description": "description", - "homepage": "home page", - "id": "id", - "logo": "logo", - "name": "name", - "policy": "policy", - "redirect": "redirect uri", - "scope": "scope", - "terms": "terms of service" - }, - "matched-search": "Matched search:", - "new": "New Client", - "no-clients": "There are no registered clients on this server.", - "no-matches": "There are no clients that match your search criteria.", - "no-redirect": "NO REDIRECT URI", - "registered": "Registrered", - "search": "Search...", - "whitelist": "Whitelist", - "unknown": "at an unknown time" - }, - "manage": "Manage Clients", - "more-info": { - "contacts": "Administrative Contacts:", - "home": "Home Page:", - "more": "more information", - "policy": "Policy:", - "terms": "Terms of Service:" - }, - "newClient": "New Client" - }, - "common": { - "cancel": "Cancel", - "client": "Client", - "clients": "Clients", - "close": "Close", - "delete": "Delete", - "description": "Description", - "dynamically-registered": "This client was dynamically registered", - "edit": "Edit", - "expires": "Expires:", - "information": "Information", - "new": "New", - "not-yet-implemented": "Not Yet Implemented", - "not-yet-implemented-content": "The value of this field will be saved with the client, but the server does not currently process anything with it. Future versions of the server library will make use of this.", - "revoke": "Revoke", - "save": "Save", - "scopes": "Scopes", - "statistics": "Statistics" - }, - "dynreg": { - "client-id-placeholder": "Enter Client ID", - "configuration-url": "Client Configuration URL", - "edit-dynamically-registered": "Edit a Dynamically Registered Client", - "edit-existing": "Edit an existing client", - "edit-existing-help": "Paste in your client ID and registration access token to access the client.", - "invalid-access-token": "Invalid client or registration access token.", - "new-client": "Register a new client", - "or": " - OR - ", - "regtoken-placeholder": "Enter Registration Access Token", - "warning": "Warning! You MUST protect your Client ID, Client Secret (if provided), and your Registration Access Token. If you lose your Client ID or Registration Access Token, you will no longer have access to your client's registration records and you will need to register a new client.", - "will-be-generated": "Will be generated" - }, - "grant": { - "manage-approved-sites": "Manage Approved Sites", - "refresh": "Refresh", - "grant-table": { - "active-tokens": "Number of currently active access tokens", - "application": "Application", - "approved-sites": "Approved Sites", - "authorized": "Authorized:", - "dynamically-registered": "This client was dynamically registered", - "expires": "Expires:", - "last-accessed": "Last accessed:", - "never": "Never", - "no-sites": "You have not approved any sites.", - "no-whitelisted": "You have not accessed any whitelisted sites.", - "pre-approved": "These are sites that have been pre-approved by an administrator.", - "text": "These are sites you have approved manually. If the same site asks for the same access in the future, it will be granted without prompting.", - "unknown": "Unknown", - "whitelist-note": "NOTE: If you revoke them here, they will automatically be re-approved on your next visit wthout prompting.", - "whitelisted-site": "This site was whitelisted by an adminstrator", - "whitelisted-sites": "Whitelisted Sites" - } - }, - "rsreg": { - "resource-id-placeholder": "Enter Resource ID", - "configuration-url": "Client Configuration URL", - "edit": "Edit Protected Resource", - "edit-existing": "Edit an existing protected resource", - "edit-existing-help": "Paste in your ID and registration access token to access the resource's properties.", - "invalid-access-token": "Invalid client or registration access token.", - "new": "New Protected Resource", - "new-resource": "Register a new protected resource", - "or": " - OR - ", - "regtoken-placeholder": "Enter Registration Access Token", - "will-be-generated": "Will be generated", - "warning": "Warning! You MUST protect your Client ID, Client Secret (if provided), and your Registration Access Token. If you lose your Client ID or Registration Access Token, you will no longer have access to your client's registration records and you will need to register a new client.", - "client-form": { - "scope-help": "Scopes that this resource will be able to introspect tokens for." - } - }, - "scope": { - "manage": "Manage System Scopes", - "scope-list": { - "no-scopes": "NO SCOPES" - }, - "system-scope-form": { - "default": "default scope", - "default-help": "Newly-created clients get this scope by default?", - "description-help": "Human-readable text description", - "description-placeholder": "Type a description", - "restricted": "restricted", - "restricted-help": "Restricted scopes are only usable by system administrators and are unavailable to dynamically registered clients and protected resources", - "edit": "Edit Scope", - "icon": "Icon", - "new": "New Scope", - "select-icon": "Select an icon", - "structured": "is a structured scope", - "structured-help": "Is the scope structured with structured values like base:extension?", - "structured-param-help": "Human-readable description of the structured parameter", - "subject-type": "Subject Type", - "value": "Scope value", - "value-help": "Single string with no spaces", - "value-placeholder": "scope" - }, - "system-scope-table": { - "confirm": "Are you sure sure you would like to delete this scope? Clients that have this scope will still be able to ask for it.", - "new": "New Scope", - "text": "There are no system scopes defined. Clients may still have custom scopes.", - "tooltip-restricted": "This scope can be used only by adminisrtators. It is not available for dynamic registration.", - "tooltip-default": "This scope is automatically assigned to newly registered clients." - } - }, - "token": { - "manage": "Manage Active Tokens", - "token-table": { - "access-tokens": "Access Tokens", - "associated-id": "This access token was issued with an associated ID token.", - "associated-refresh": "This access token was issued with an associated refresh token.", - "click-to-display": "Click to display full token value", - "confirm": "Are you sure sure you would like to revoke this token?", - "confirm-refresh": "Are you sure sure you would like to revoke this refresh token and its associated access tokens?", - "expires": "Expires", - "no-access": "There are no active access tokens.", - "no-refresh": "There are no active refresh tokens.", - "number-of-tokens": "Number of associated access tokens", - "refresh-tokens": "Refresh Tokens", - "text": "Access tokens are usually short-lived and provide clients with access to specific resources. ID Tokens are specialized access tokens to facilitate log on using OpenID Connect.", - "text-refresh": "Refresh tokens are usually long-lived and provide clients with the ability to get new access tokens without end-user involvement.", - "token-info": "Token Information" - } - }, - "whitelist": { - "confirm": "Are you sure you want to delete this whitelist entry?", - "edit": "Edit Whitelist", - "manage": "Manage Whitelisted Sites", - "new": "New Whitelist", - "whitelist": "Whitelist", - "whitelist-form": { - "allowed-scopes": "Allowed Scopes", - "edit": "Edit Whitelisted Site", - "new": "New Whitelisted Site", - "scope-help": "List of scopes that will be automatically approved when this client makes a request", - "scope-placeholder": "new scope" - }, - "whitelist-table": { - "no-sites": "There are no whitelisted sites. Use the whitelist button on the client management page to create one." - } - }, - "policy" : { - "resource-sets": "Resource Sets", - "edit-policies": "Edit Policies", - "new-policy": "New Policy", - "edit-policy": "Edit Policy", - "loading-policies": "Policies", - "loading-policy": "Policy", - "loading-rs": "Resource Set", - "rs-table": { - "confirm": "Are you sure you want to delete this resource set?", - "no-resource-sets": "There are no resource sets registered. Introduce a protected to this authorization server to let it register some.", - "scopes": "Scopes", - "shared-with": "Shared with:", - "shared-nobody": "NOBODY", - "shared-nobody-tooltip": "This resource is not accessible by anyone else, edit the policies and share it with someone.", - "sharing": "Sharing Policies" - }, - "policy-table": { - "new": "Add New Policy", - "return": "Return to list", - "edit": "Edit Policy", - "confirm": "Are you sure you want to delete this policy?", - "delete": "Delete", - "no-policies": "There are no policies for this resource set: This resource set is inaccessible by others.", - "required-claims": "Required Claims", - "required-claims-info": "Users that you share this resource will with need to be able to present the following claims in order to access the resource.", - "remove": "Remove", - "issuers": "Issuers", - "claim": "Claim", - "value": "Value" - }, - "policy-form": { - "email-address": "email address", - "share-email": "Share with email address", - "new": "New Policy", - "edit": "Edit Policy" - }, - "webfinger-error": "Error", - "webfinger-error-description": "The server was unable to find an identity provider for __email__." - }, - "copyright": "Powered by MITREid Connect {0} © 2015 The MITRE Corporation and MIT KIT..", - "about": { - "title": "About", - "body": "\nThis OpenID Connect service is built from the MITREid Connect Open Source project, from \nThe MITRE Corporation and the MIT Kerberos and Internet Trust Consortium.\n

    \n

    \nMore information about the project can be found at \nMITREid Connect on GitHub. \nThere, you can submit bug reports, give feedback, or even contribute code patches for additional features you'd like to see." - }, - "statistics": { - "title": "Statistics", - "number_users": "Number of users: {0}", - "number_clients": "Authorized clients: {0}", - "number_approvals": "Approved sites: {0}" - }, - "home": { - "title": "Home", - "welcome": { - "title": "Welcome!", - "body": "\nOpenID Connect is an internet-scale federated identity protocol built on top of the OAuth2 authorization framework. \nOpenID Connect lets you log into a remote site using your identity without exposing your credentials, like a username and password.

    \n

    Learn more »" - }, - "more": "More", - "about": { - "title": "About", - "body": "This OpenID Connect service is built from the MITREid Connect Open Source project, from \nThe MITRE Corporation and the MIT Kerberos and Internet Trust Consortium." - }, - "contact": { - "title": "Contact", - "body": "\nFor more information or support, contact the administrators of this system.

    \n

    Email »" - }, - "statistics": { - "title": "Current Statistics", - "loading": "Loading...", - "number_users": "Number of users: {0}", - "number_clients": "Authorized clients: {0}", - "number_approvals": "Approved sites: {0}" - } - }, - "contact": { - "title": "Contact", - "body": "To report bugs with the MITREid Connect software itself, use the \nGitHub issue tracker. \nFor problems relating to this server, contact the server's administrator." - }, - "topbar": { - "about": "About", - "contact": "Contact", - "statistics": "Statistics", - "home": "Home", - "login": "Log in", - "logout": "Log out" - }, - "sidebar": { - "administrative": { - "title": "Administrative", - "manage_clients": "Manage Clients", - "whitelisted_clients": "Whitelisted Clients", - "blacklisted_clients": "Blacklisted Clients", - "system_scopes": "System Scopes" - }, - "personal": { - "title": "Personal", - "approved_sites": "Manage Approved Sites", - "active_tokens": "Manage Active Tokens", - "profile_information": "View Profile Information", - "resource_policies": "Manage Protected Resource Policies" - }, - "developer": { - "title": "Developer", - "client_registration": "Self-service client registration", - "resource_registration": "Self-service protected resource registration" - } - }, - "manage": { - "ok": "OK", - "loading": "Loading", - "title": "Management Console" - }, - "approve": { - "dynamically-registered-unknown": "at an unknown time", - "title": "Approve Access", - "error": { - "not_granted": "Access could not be granted." - }, - "required_for": "Approval Required for", - "dynamically_registered": "This client was dynamically registered {0}.", - "caution": { - "title": "Caution", - "message": { - "none": "It has never been approved previously.", - "singular": "It has been approved {0} time previously.", - "plural": "It has been approved {0} times previously." - } - }, - "more_information": "more information", - "home_page": "Home page", - "policy": "Policy", - "terms": "Terms of Service", - "contacts": "Administrative Contacts", - "warning": "Warning", - "no_redirect_uri": "This client does not have any redirect URIs registered and someone could be using a malicious URI here.", - "redirect_uri": "You will be redirected to the following page if you click Approve: {0}", - "pairwise": "This client uses a pairwise identifier, which makes it more difficult to correlate your identity between sites.", - "no_scopes": "This client does not have any scopes registered and is therefore allowed to request any scopes available on the system. Proceed with caution.", - "access_to": "Access to", - "remember": { - "title": "Remember this decision", - "until_revoke": "remember this decision until I revoke it", - "one_hour": "remember this decision for one hour", - "next_time": "prompt me again next time" - }, - "do_authorize": "Do you authorize", - "label": { - "authorize": "Authorize", - "deny": "Deny" - } - } - -} \ No newline at end of file diff --git a/uma-server-webapp/src/main/webapp/resources/js/locale/en/uma.json b/uma-server-webapp/src/main/webapp/resources/js/locale/en/uma.json new file mode 100644 index 0000000000..69ff2e1869 --- /dev/null +++ b/uma-server-webapp/src/main/webapp/resources/js/locale/en/uma.json @@ -0,0 +1,59 @@ +{ + "admin": { + "policies": "Manage Protected Resource Policies" + }, + "policy" : { + "resource-sets": "Resource Sets", + "edit-policies": "Edit Policies", + "new-policy": "New Policy", + "edit-policy": "Edit Policy", + "loading-policies": "Policies", + "loading-policy": "Policy", + "loading-rs": "Resource Set", + "rs-table": { + "confirm": "Are you sure you want to delete this resource set?", + "no-resource-sets": "There are no resource sets registered. Introduce a protected to this authorization server to let it register some.", + "scopes": "Scopes", + "shared-with": "Shared with:", + "shared-nobody": "NOBODY", + "shared-nobody-tooltip": "This resource is not accessible by anyone else, edit the policies and share it with someone.", + "sharing": "Sharing Policies" + }, + "policy-table": { + "new": "Add New Policy", + "return": "Return to list", + "edit": "Edit Policy", + "confirm": "Are you sure you want to delete this policy?", + "delete": "Delete", + "no-policies": "There are no policies for this resource set: This resource set is inaccessible by others.", + "required-claims": "Required Claims", + "required-claims-info": "Users that you share this resource will with need to be able to present the following claims in order to access the resource.", + "remove": "Remove", + "issuers": "Issuers", + "claim": "Claim", + "value": "Value" + }, + "policy-form": { + "email-address": "email address", + "share-email": "Share with email address", + "new": "New Policy", + "edit": "Edit Policy", + "claim-name": "claim name", + "friendly-claim-name": "friendly claim name", + "claim-value": "claim value", + "value-type-text": "Text", + "value-type-number": "Number", + "clear-all": "Clear all claims", + "clear-all-confirm": "Are you sure you want to clear all claims from this policy?" + }, + "webfinger-error": "Error", + "webfinger-error-description": "The server was unable to find an identity provider for __email__.", + "advanced-error": "Error", + "advanced-error-description": "There was an error saving your advanced claim. Did you fill in all required fields?" + }, + "sidebar": { + "personal": { + "resource_policies": "Manage Protected Resource Policies" + } + } +} \ No newline at end of file diff --git a/uma-server-webapp/src/main/webapp/resources/js/locale/zh/uma.json b/uma-server-webapp/src/main/webapp/resources/js/locale/zh/uma.json new file mode 100644 index 0000000000..e2444c4ea1 --- /dev/null +++ b/uma-server-webapp/src/main/webapp/resources/js/locale/zh/uma.json @@ -0,0 +1,59 @@ +{ + "admin": { + "policies": "管理受保护资源的政策" + }, + "policy" : { + "resource-sets": "资源集", + "edit-policies": "编辑政策", + "new-policy": "新建政策", + "edit-policy": "编辑政策", + "loading-policies": "政策", + "loading-policy": "政策", + "loading-rs": "资源集", + "rs-table": { + "confirm": "确定要删除该资源?", + "no-resource-sets": "尚未有已注册的资源集。您可在此授权服务器中注册一个。", + "scopes": "范围", + "shared-with": "共享给:", + "shared-nobody": "不共享", + "shared-nobody-tooltip": "此资源别人无法访问,请编辑政策使其与其他人共享。", + "sharing": "共享政策" + }, + "policy-table": { + "new": "新建政策", + "return": "返回到列表", + "edit": "编辑政策", + "confirm": "确定要删除该政策?", + "delete": "删除", + "no-policies": "此资源集尚未有政策:别人无法访问此资源集。", + "required-claims": "必须的声明", + "required-claims-info": "与您共享此资源的用户必须具备以下声明,才能访问该资源。", + "remove": "移除", + "issuers": "签发者", + "claim": "声明项", + "value": "值" + }, + "policy-form": { + "email-address": "email地址", + "share-email": "连带email地址共享", + "new": "新建政策", + "edit": "编辑政策", + "claim-name": "声明项名称", + "friendly-claim-name": "声明的显示名", + "claim-value": "声明的值", + "value-type-text": "文本", + "value-type-number": "数字", + "clear-all": "清除全部声明", + "clear-all-confirm": "您是否要从此政策中清除全部声明?" + }, + "webfinger-error": "错误", + "webfinger-error-description": "服务器无法找到__email__的身份提供者。", + "advanced-error": "错误", + "advanced-error-description": "保存高级声明时出错。您是否填写了全部必填项?" + }, + "sidebar": { + "personal": { + "resource_policies": "管理受保护资源的政策" + } + } +} \ No newline at end of file diff --git a/uma-server-webapp/src/main/webapp/resources/js/locale/zh_CN/uma.json b/uma-server-webapp/src/main/webapp/resources/js/locale/zh_CN/uma.json new file mode 100644 index 0000000000..e2444c4ea1 --- /dev/null +++ b/uma-server-webapp/src/main/webapp/resources/js/locale/zh_CN/uma.json @@ -0,0 +1,59 @@ +{ + "admin": { + "policies": "管理受保护资源的政策" + }, + "policy" : { + "resource-sets": "资源集", + "edit-policies": "编辑政策", + "new-policy": "新建政策", + "edit-policy": "编辑政策", + "loading-policies": "政策", + "loading-policy": "政策", + "loading-rs": "资源集", + "rs-table": { + "confirm": "确定要删除该资源?", + "no-resource-sets": "尚未有已注册的资源集。您可在此授权服务器中注册一个。", + "scopes": "范围", + "shared-with": "共享给:", + "shared-nobody": "不共享", + "shared-nobody-tooltip": "此资源别人无法访问,请编辑政策使其与其他人共享。", + "sharing": "共享政策" + }, + "policy-table": { + "new": "新建政策", + "return": "返回到列表", + "edit": "编辑政策", + "confirm": "确定要删除该政策?", + "delete": "删除", + "no-policies": "此资源集尚未有政策:别人无法访问此资源集。", + "required-claims": "必须的声明", + "required-claims-info": "与您共享此资源的用户必须具备以下声明,才能访问该资源。", + "remove": "移除", + "issuers": "签发者", + "claim": "声明项", + "value": "值" + }, + "policy-form": { + "email-address": "email地址", + "share-email": "连带email地址共享", + "new": "新建政策", + "edit": "编辑政策", + "claim-name": "声明项名称", + "friendly-claim-name": "声明的显示名", + "claim-value": "声明的值", + "value-type-text": "文本", + "value-type-number": "数字", + "clear-all": "清除全部声明", + "clear-all-confirm": "您是否要从此政策中清除全部声明?" + }, + "webfinger-error": "错误", + "webfinger-error-description": "服务器无法找到__email__的身份提供者。", + "advanced-error": "错误", + "advanced-error-description": "保存高级声明时出错。您是否填写了全部必填项?" + }, + "sidebar": { + "personal": { + "resource_policies": "管理受保护资源的政策" + } + } +} \ No newline at end of file diff --git a/uma-server-webapp/src/main/webapp/resources/js/locale/zh_TW/uma.json b/uma-server-webapp/src/main/webapp/resources/js/locale/zh_TW/uma.json new file mode 100644 index 0000000000..5232328326 --- /dev/null +++ b/uma-server-webapp/src/main/webapp/resources/js/locale/zh_TW/uma.json @@ -0,0 +1,59 @@ +{ + "admin": { + "policies": "管理受保護資源的政策" + }, + "policy" : { + "resource-sets": "資源集", + "edit-policies": "編輯政策", + "new-policy": "新建政策", + "edit-policy": "編輯政策", + "loading-policies": "政策", + "loading-policy": "政策", + "loading-rs": "資源集", + "rs-table": { + "confirm": "確定要刪除該資源?", + "no-resource-sets": "尚未有已注冊的資源集。您可在此授權伺服器中注冊一個。", + "scopes": "范圍", + "shared-with": "共享給:", + "shared-nobody": "不共享", + "shared-nobody-tooltip": "此資源別人無法訪問,請編輯政策使其與其他人共享。", + "sharing": "共享政策" + }, + "policy-table": { + "new": "新建政策", + "return": "返回到列表", + "edit": "編輯政策", + "confirm": "確定要刪除該政策?", + "delete": "刪除", + "no-policies": "此資源集尚未有政策:別人無法訪問此資源集。", + "required-claims": "必須的聲明", + "required-claims-info": "與您共享此資源的用戶必須具備以下聲明,才能訪問該資源。", + "remove": "移除", + "issuers": "簽發者", + "claim": "聲明項", + "value": "值" + }, + "policy-form": { + "email-address": "email地址", + "share-email": "連帶email地址共享", + "new": "新建政策", + "edit": "編輯政策", + "claim-name": "聲明項名稱", + "friendly-claim-name": "聲明的顯示名", + "claim-value": "聲明的值", + "value-type-text": "文本", + "value-type-number": "數字", + "clear-all": "清除全部聲明", + "clear-all-confirm": "您是否要從此政策中清除全部聲明?" + }, + "webfinger-error": "錯誤", + "webfinger-error-description": "伺服器無法找到__email__的身份提供者。", + "advanced-error": "錯誤", + "advanced-error-description": "保存高級聲明時出錯。您是否填寫了全部必填項?" + }, + "sidebar": { + "personal": { + "resource_policies": "管理受保護資源的政策" + } + } +} \ No newline at end of file diff --git a/uma-server-webapp/src/main/webapp/resources/js/policy.js b/uma-server-webapp/src/main/webapp/resources/js/policy.js index 83ed99b9c4..6a3b6420c0 100644 --- a/uma-server-webapp/src/main/webapp/resources/js/policy.js +++ b/uma-server-webapp/src/main/webapp/resources/js/policy.js @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -187,6 +186,7 @@ var ResourceSetView = Backbone.View.extend({ var _self = this; this.model.destroy({ + dataType: false, processData: false, success:function () { _self.$el.fadeTo("fast", 0.00, function () { //fade $(this).slideUp("fast", function () { //slide up @@ -349,6 +349,7 @@ var PolicyView = Backbone.View.extend({ if (confirm($.t('policy.policy-table.confirm'))) { var _self = this; this.model.destroy({ + dataType: false, processData: false, success:function () { _self.$el.fadeTo("fast", 0.00, function () { //fade $(this).slideUp("fast", function () { //slide up @@ -404,11 +405,14 @@ var PolicyFormView = Backbone.View.extend({ this.template = _.template($('#tmpl-policy-form').html()); } - this.scopeCollection = new Backbone.Collection(); + this.issuerCollection = new Backbone.Collection(); + }, events:{ - 'click .btn-share': 'addClaim', + 'click .btn-share': 'addWebfingerClaim', + 'click .btn-share-advanced': 'addAdvancedClaim', + 'click .btn-clear': 'clearAllClaims', 'click .btn-save': 'savePolicy', 'click .btn-cancel': 'cancel' }, @@ -437,7 +441,7 @@ var PolicyFormView = Backbone.View.extend({ }); }, - addClaim:function(e) { + addWebfingerClaim:function(e) { e.preventDefault(); // post to the webfinger helper and get the response back @@ -446,7 +450,12 @@ var PolicyFormView = Backbone.View.extend({ var email = $('#email', this.el).val(); - var base = $('base').attr('href'); + $('#loadingbox').sheet('show'); + $('#loading').html( + 'Looking up identity provider...' + ); + + var base = $('base').attr('href'); $.getJSON(base + '/api/emailsearch?' + $.param({'identifier': email}), function(data) { // grab the current state of the scopes checkboxes just in case @@ -458,10 +467,14 @@ var PolicyFormView = Backbone.View.extend({ }, {trigger: false}); _self.render(); + + $('#loadingbox').sheet('hide'); }).error(function(jqXHR, textStatus, errorThrown) { console.log("An error occurred when doing a webfinger lookup", errorThrown); + $('#loadingbox').sheet('hide'); + //Display an alert with an error message $('#modalAlert div.modal-header').html($.t('policy.webfinger-error')); $('#modalAlert div.modal-body').html($.t('policy.webfinger-error-description', {email: email})); @@ -475,6 +488,96 @@ var PolicyFormView = Backbone.View.extend({ }, + addAdvancedClaim:function(e) { + e.preventDefault(); + + var name = $('#name', this.el).val(); + var friendly = $('#friendly-name', this.el).val(); + var rawValue = $('#value', this.el).val(); + var valueType = $('#value-type', this.el).val(); + var value = null; + if (valueType == 'number') { + value = Number(rawValue); + } else if (valueType == 'boolean') { + value = (rawValue.toLowerCase() == 'true'); + } else if (valueType == 'json') { + value = JSON.parse(rawValue); + } else { + // treat it as a string, the default + value = rawValue; + } + + var issuers = this.issuerCollection.pluck('item'); + + console.log(name, friendly, rawValue, valueType, value, issuers); + + if (!_.isEmpty(issuers) + && name + && value) { + // we've got a valid claim, add it to our set + // grab the current state of the scopes checkboxes just in case + var scopes = $('#scopes input[type="checkbox"]:checked').map(function(idx, elem) { return $(elem).val(); }).get(); + + var claimsRequired = this.model.get('claimsRequired'); + if (!claimsRequired) { + claimsRequired = []; + } + claimsRequired.push({ + name: name, + friendlyName: friendly, + value: value, + issuer: issuers + }); + + this.model.set({ + scopes: scopes, + claimsRequired: claimsRequired + }, {trigger: false}); + + $('#name', this.el).val(''); + $('#friendly-name', this.el).val(''); + $('#value', this.el).val(''); + $('#value-type', this.el).val('text'); + + this.render(); + + // re-select the advanced tab + $('a[data-target="#policy-advanced-tab"]', this.el).tab('show') + + } else { + // something is missing + $('#loadingbox').sheet('hide'); + + //Display an alert with an error message + $('#modalAlert div.modal-header').html($.t('policy.advanced-error')); + $('#modalAlert div.modal-body').html($.t('policy.advanced-error-description')); + + $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog + "backdrop" : "static", + "keyboard" : true, + "show" : true // ensure the modal is shown immediately + }); + } + }, + + clearAllClaims:function(e) { + e.preventDefault(); + + if (confirm($.t('policy.policy-form.clear-all-confirm'))) { + + var scopes = $('#scopes input[type="checkbox"]:checked').map(function(idx, elem) { return $(elem).val(); }).get(); + + var claimsRequired = []; + + this.model.set({ + scopes: scopes, + claimsRequired: claimsRequired + }, {trigger: false}); + + this.render(); + } + }, + savePolicy:function(e) { e.preventDefault(); @@ -531,8 +634,153 @@ var PolicyFormView = Backbone.View.extend({ this.$el.html(this.template({policy: json, rs: rs})); + // build and bind issuer view + var issuerView = new ListWidgetView({ + placeholder: $.t('policy.policy-form.issuer-placeholder'), + helpBlockText: $.t('policy.policy-form.issuer-help'), + collection: this.issuerCollection}); + $("#issuers .controls",this.el).html(issuerView.render().el); + $(this.el).i18n(); return this; } -}); \ No newline at end of file +}); + + +ui.routes.push({path: "user/policy", name: "policy", callback: + function() { + + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:$.t('admin.home'), href:""}, + {text:$.t('policy.resource-sets'), href:"manage/#user/policy"} + ]); + + this.updateSidebar('user/policy'); + + var view = new ResourceSetListView({model: this.resourceSetList, clientList: this.clientList, systemScopeList: this.systemScopeList}); + + view.load(function() { + $('#content').html(view.render().el); + setPageTitle($.t('policy.resource-sets')); + }); + + } +}); + +ui.routes.push({path: "user/policy/:rsid", name: "editPolicies", callback: + function(rsid) { + + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:$.t('admin.home'), href:""}, + {text:$.t('policy.resource-sets'), href:"manage/#user/policy"}, + {text:$.t('policy.edit-policies'), href:"manage/#user/policy/" + rsid} + ]); + + this.updateSidebar('user/policy'); + + var rs = this.resourceSetList.get(rsid); + var policies = null; + if (rs == null) { + // need to load it directly + policies = new PolicyCollection([], {rsid: rsid}); + rs = new ResourceSetModel({id: rsid}); + this.resourceSetList.add(rs); // it will be loaded below, don't need to load it again in the future + } else { + // the resource set is loaded, preload the claims + policies = new PolicyCollection(rs.get('policies'), {rsid: rsid}); + policies.isFetched = true; + } + + var view = new PolicyListView({model: policies, rs: rs, systemScopeList: this.systemScopeList}); + + view.load(function() { + $('#content').html(view.render().el); + setPageTitle($.t('policy.edit-policy')); + }); + + } +}); + +ui.routes.push({path: "user/policy/:rsid/new", name: "newPolicy", callback: + function(rsid) { + + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:$.t('admin.home'), href:""}, + {text:$.t('policy.resource-sets'), href:"manage/#user/policy"}, + {text:$.t('policy.edit-policies'), href:"manage/#user/policy/" + rsid}, + {text:$.t('policy.new-policy'), href:"manage/#user/policy/" + rsid + "/new"} + ]); + + this.updateSidebar('user/policy'); + + var policy = policy = new PolicyModel({}, {rsid: rsid}); + + var rs = this.resourceSetList.get(rsid); + if (rs == null) { + // need to load it directly + rs = new ResourceSetModel({id: rsid}); + this.resourceSetList.add(rs); // it will be loaded below, don't need to load it again in the future + } + + var view = new PolicyFormView({model: policy, rs: rs, systemScopeList: this.systemScopeList}); + + view.load(function() { + $('#content').html(view.render().el); + setPageTitle($.t('policy.edit-policy')); + }); + } +}); + +ui.routes.push({path: "user/policy/:rsid/:pid", name: "editPolicy", callback: + function(rsid, pid) { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:$.t('admin.home'), href:""}, + {text:$.t('policy.resource-sets'), href:"manage/#user/policy"}, + {text:$.t('policy.edit-policies'), href:"manage/#user/policy/" + rsid}, + {text:$.t('policy.edit-policy'), href:"manage/#user/policy/" + rsid + "/" + pid} + ]); + + this.updateSidebar('user/policy'); + + var rs = this.resourceSetList.get(rsid); + var policy = null; + if (rs == null) { + // need to load it directly + policy = new PolicyModel({id: pid}, {rsid: rsid}); + rs = new ResourceSetModel({id: rsid}); + this.resourceSetList.add(rs); // it will be loaded below, don't need to load it again in the future + } else { + // the resource set is loaded, preload the claims + _.each(rs.get('policies'), function(p) { + if (p.id == pid) { + policy = new PolicyModel(p, {rsid: rsid}); + policy.isFetched = true; + } + }); + if (policy == null) { + // need to load it directly + policy = new PolicyModel({id: pid}, {rsid: rsid}); + } + } + + var view = new PolicyFormView({model: policy, rs: rs, systemScopeList: this.systemScopeList}); + + view.load(function() { + $('#content').html(view.render().el); + setPageTitle($.t('policy.edit-policy')); + }); + + + } +}); + +ui.templates.push('resources/template/policy.html'); + +ui.init.push(function(app) { + app.resourceSetList = new ResourceSetCollection(); +}); diff --git a/uma-server-webapp/src/main/webapp/resources/template/policy.html b/uma-server-webapp/src/main/webapp/resources/template/policy.html index 7322b3daa0..576da1b1ad 100644 --- a/uma-server-webapp/src/main/webapp/resources/template/policy.html +++ b/uma-server-webapp/src/main/webapp/resources/template/policy.html @@ -1,6 +1,5 @@ + 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. + --> 4.0.0 org.mitre openid-connect-parent - 1.2.0-RC2-SNAPSHOT + 1.3.5-SNAPSHOT .. uma-server @@ -48,4 +47,4 @@ openid-connect-client - \ No newline at end of file + diff --git a/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaPermissionRepository.java b/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaPermissionRepository.java index e2a78aba53..6f460d200d 100644 --- a/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaPermissionRepository.java +++ b/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaPermissionRepository.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -23,7 +22,6 @@ import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; -import org.apache.http.MethodNotSupportedException; import org.mitre.uma.model.Permission; import org.mitre.uma.model.PermissionTicket; import org.mitre.uma.model.ResourceSet; @@ -39,11 +37,11 @@ @Repository public class JpaPermissionRepository implements PermissionRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager em; - + @Override - @Transactional + @Transactional(value="defaultTransactionManager") public PermissionTicket save(PermissionTicket p) { return JpaUtil.saveOrUpdate(p.getId(), em, p); } @@ -71,7 +69,7 @@ public Collection getAll() { * @see org.mitre.uma.repository.PermissionRepository#saveRawPermission(org.mitre.uma.model.Permission) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public Permission saveRawPermission(Permission p) { return JpaUtil.saveOrUpdate(p.getId(), em, p); } @@ -98,7 +96,7 @@ public Collection getPermissionTicketsForResourceSet(ResourceS * @see org.mitre.uma.repository.PermissionRepository#remove(org.mitre.uma.model.PermissionTicket) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(PermissionTicket ticket) { PermissionTicket found = getByTicket(ticket.getTicket()); if (found != null) { diff --git a/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaResourceSetRepository.java b/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaResourceSetRepository.java index 4eefff9ffb..e19236c399 100644 --- a/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaResourceSetRepository.java +++ b/uma-server/src/main/java/org/mitre/uma/repository/impl/JpaResourceSetRepository.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -38,12 +37,12 @@ @Repository public class JpaResourceSetRepository implements ResourceSetRepository { - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager em; private static Logger logger = LoggerFactory.getLogger(JpaResourceSetRepository.class); - + @Override - @Transactional + @Transactional(value="defaultTransactionManager") public ResourceSet save(ResourceSet rs) { return JpaUtil.saveOrUpdate(rs.getId(), em, rs); } @@ -54,7 +53,7 @@ public ResourceSet getById(Long id) { } @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void remove(ResourceSet rs) { ResourceSet found = getById(rs.getId()); if (found != null) { @@ -78,7 +77,7 @@ public Collection getAllForOwnerAndClient(String owner, String clie query.setParameter(ResourceSet.PARAM_CLIENTID, clientId); return query.getResultList(); } - + @Override public Collection getAll() { TypedQuery query = em.createNamedQuery(ResourceSet.QUERY_ALL, ResourceSet.class); diff --git a/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultPermissionService.java b/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultPermissionService.java index c4a9ba0b47..8b9c379e48 100644 --- a/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultPermissionService.java +++ b/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultPermissionService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -40,35 +39,35 @@ public class DefaultPermissionService implements PermissionService { @Autowired private PermissionRepository repository; - + @Autowired private SystemScopeService scopeService; - + private Long permissionExpirationSeconds = 60L * 60L; // 1 hr - + /* (non-Javadoc) * @see org.mitre.uma.service.PermissionService#create(org.mitre.uma.model.ResourceSet, java.util.Set) */ @Override public PermissionTicket createTicket(ResourceSet resourceSet, Set scopes) { - + // check to ensure that the scopes requested are a subset of those in the resource set - + if (!scopeService.scopesMatch(resourceSet.getScopes(), scopes)) { throw new InsufficientScopeException("Scopes of resource set are not enough for requested permission."); } - + Permission perm = new Permission(); perm.setResourceSet(resourceSet); perm.setScopes(scopes); - + PermissionTicket ticket = new PermissionTicket(); ticket.setPermission(perm); ticket.setTicket(UUID.randomUUID().toString()); ticket.setExpiration(new Date(System.currentTimeMillis() + permissionExpirationSeconds * 1000L)); - + return repository.save(ticket); - + } /* (non-Javadoc) @@ -89,9 +88,9 @@ public PermissionTicket updateTicket(PermissionTicket ticket) { } else { return null; } - + } - - + + } diff --git a/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultResourceSetService.java b/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultResourceSetService.java index 269c29831e..a5c3e5ec4b 100644 --- a/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultResourceSetService.java +++ b/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultResourceSetService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -43,31 +42,31 @@ public class DefaultResourceSetService implements ResourceSetService { private static final Logger logger = LoggerFactory.getLogger(DefaultResourceSetService.class); - + @Autowired private ResourceSetRepository repository; - + @Autowired private OAuth2TokenRepository tokenRepository; - + @Autowired private PermissionRepository ticketRepository; @Override public ResourceSet saveNew(ResourceSet rs) { - + if (rs.getId() != null) { throw new IllegalArgumentException("Can't save a new resource set with an ID already set to it."); } - + if (!checkScopeConsistency(rs)) { throw new IllegalArgumentException("Can't save a resource set with inconsistent claims."); } - + ResourceSet saved = repository.save(rs); - + return saved; - + } @Override @@ -80,22 +79,22 @@ public ResourceSet update(ResourceSet oldRs, ResourceSet newRs) { if (oldRs.getId() == null || newRs.getId() == null || !oldRs.getId().equals(newRs.getId())) { - + throw new IllegalArgumentException("Resource set IDs mismatched"); - + } if (!checkScopeConsistency(newRs)) { throw new IllegalArgumentException("Can't save a resource set with inconsistent claims."); } - + newRs.setOwner(oldRs.getOwner()); // preserve the owner tag across updates newRs.setClientId(oldRs.getClientId()); // preserve the client id across updates - + ResourceSet saved = repository.save(newRs); - + return saved; - + } @Override @@ -105,13 +104,13 @@ public void remove(ResourceSet rs) { for (OAuth2AccessTokenEntity token : tokens) { tokenRepository.removeAccessToken(token); } - + // find all outstanding tickets issued against this resource set and revoke them too Collection tickets = ticketRepository.getPermissionTicketsForResourceSet(rs); for (PermissionTicket ticket : tickets) { ticketRepository.remove(ticket); } - + repository.remove(rs); } @@ -124,7 +123,7 @@ public Collection getAllForOwner(String owner) { public Collection getAllForOwnerAndClient(String owner, String clientId) { return repository.getAllForOwnerAndClient(owner, clientId); } - + private boolean checkScopeConsistency(ResourceSet rs) { if (rs.getPolicies() == null) { // nothing to check, no problem! @@ -146,5 +145,5 @@ private boolean checkScopeConsistency(ResourceSet rs) { public Collection getAllForClient(ClientDetailsEntity client) { return repository.getAllForClient(client.getClientId()); } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultUmaTokenService.java b/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultUmaTokenService.java index 4d32e79056..62bd24eac0 100644 --- a/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultUmaTokenService.java +++ b/uma-server/src/main/java/org/mitre/uma/service/impl/DefaultUmaTokenService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -52,64 +51,67 @@ @Service("defaultUmaTokenService") public class DefaultUmaTokenService implements UmaTokenService { - @Autowired + @Autowired private AuthenticationHolderRepository authenticationHolderRepository; - + @Autowired private OAuth2TokenEntityService tokenService; - - @Autowired + + @Autowired private ClientDetailsEntityService clientService; - - @Autowired + + @Autowired private ConfigurationPropertiesBean config; - - @Autowired + + @Autowired private JWTSigningAndValidationService jwtService; - + @Override public OAuth2AccessTokenEntity createRequestingPartyToken(OAuth2Authentication o2auth, PermissionTicket ticket, Policy policy) { OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); authHolder.setAuthentication(o2auth); authHolder = authenticationHolderRepository.save(authHolder); - + token.setAuthenticationHolder(authHolder); - + ClientDetailsEntity client = clientService.loadClientByClientId(o2auth.getOAuth2Request().getClientId()); token.setClient(client); - + Set ticketScopes = ticket.getPermission().getScopes(); Set policyScopes = policy.getScopes(); - + Permission perm = new Permission(); perm.setResourceSet(ticket.getPermission().getResourceSet()); perm.setScopes(new HashSet<>(Sets.intersection(ticketScopes, policyScopes))); - + token.setPermissions(Sets.newHashSet(perm)); - - JWTClaimsSet claims = new JWTClaimsSet(); - - claims.setAudience(Lists.newArrayList(ticket.getPermission().getResourceSet().getId().toString())); - claims.setIssuer(config.getIssuer()); - claims.setJWTID(UUID.randomUUID().toString()); - + + JWTClaimsSet.Builder claims = new JWTClaimsSet.Builder(); + + claims.audience(Lists.newArrayList(ticket.getPermission().getResourceSet().getId().toString())); + claims.issuer(config.getIssuer()); + claims.jwtID(UUID.randomUUID().toString()); + if (config.getRqpTokenLifeTime() != null) { Date exp = new Date(System.currentTimeMillis() + config.getRqpTokenLifeTime() * 1000L); - - claims.setExpirationTime(exp); + + claims.expirationTime(exp); token.setExpiration(exp); } - - + + JWSAlgorithm signingAlgorithm = jwtService.getDefaultSigningAlgorithm(); - SignedJWT signed = new SignedJWT(new JWSHeader(signingAlgorithm), claims); - + JWSHeader header = new JWSHeader(signingAlgorithm, null, null, null, null, null, null, null, null, null, + jwtService.getDefaultSignerKeyId(), + null, null); + SignedJWT signed = new SignedJWT(header, claims.build()); + jwtService.signJwt(signed); - + token.setJwt(signed); - + tokenService.saveAccessToken(token); return token; diff --git a/uma-server/src/main/java/org/mitre/uma/service/impl/JpaRegisteredClientService.java b/uma-server/src/main/java/org/mitre/uma/service/impl/JpaRegisteredClientService.java index 4d3963d679..8ceb548e8c 100644 --- a/uma-server/src/main/java/org/mitre/uma/service/impl/JpaRegisteredClientService.java +++ b/uma-server/src/main/java/org/mitre/uma/service/impl/JpaRegisteredClientService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -26,6 +25,7 @@ import org.mitre.oauth2.model.RegisteredClient; import org.mitre.openid.connect.client.service.RegisteredClientService; import org.mitre.uma.model.SavedRegisteredClient; +import org.mitre.uma.service.SavedRegisteredClientService; import org.mitre.util.jpa.JpaUtil; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -35,18 +35,18 @@ * */ @Service -public class JpaRegisteredClientService implements RegisteredClientService { +public class JpaRegisteredClientService implements RegisteredClientService, SavedRegisteredClientService{ - @PersistenceContext + @PersistenceContext(unitName="defaultPersistenceUnit") private EntityManager em; - + /* (non-Javadoc) * @see org.mitre.openid.connect.client.service.RegisteredClientService#getByIssuer(java.lang.String) */ @Override public RegisteredClient getByIssuer(String issuer) { SavedRegisteredClient saved = getSavedRegisteredClientFromStorage(issuer); - + if (saved == null) { return null; } else { @@ -58,19 +58,19 @@ public RegisteredClient getByIssuer(String issuer) { * @see org.mitre.openid.connect.client.service.RegisteredClientService#save(java.lang.String, org.mitre.oauth2.model.RegisteredClient) */ @Override - @Transactional + @Transactional(value="defaultTransactionManager") public void save(String issuer, RegisteredClient client) { - - + + SavedRegisteredClient saved = getSavedRegisteredClientFromStorage(issuer); - + if (saved == null) { saved = new SavedRegisteredClient(); saved.setIssuer(issuer); } - + saved.setRegisteredClient(client); - + em.persist(saved); } @@ -78,7 +78,7 @@ public void save(String issuer, RegisteredClient client) { private SavedRegisteredClient getSavedRegisteredClientFromStorage(String issuer) { TypedQuery query = em.createQuery("SELECT c from SavedRegisteredClient c where c.issuer = :issuer", SavedRegisteredClient.class); query.setParameter("issuer", issuer); - + SavedRegisteredClient saved = JpaUtil.getSingleResult(query.getResultList()); return saved; } @@ -86,9 +86,10 @@ private SavedRegisteredClient getSavedRegisteredClientFromStorage(String issuer) /** * @return */ + @Override public Collection getAll() { TypedQuery query = em.createQuery("SELECT c from SavedRegisteredClient c", SavedRegisteredClient.class); return query.getResultList(); } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/service/impl/MatchAllClaimsOnAnyPolicy.java b/uma-server/src/main/java/org/mitre/uma/service/impl/MatchAllClaimsOnAnyPolicy.java index 87ee2f6939..7d480bf619 100644 --- a/uma-server/src/main/java/org/mitre/uma/service/impl/MatchAllClaimsOnAnyPolicy.java +++ b/uma-server/src/main/java/org/mitre/uma/service/impl/MatchAllClaimsOnAnyPolicy.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -29,9 +28,9 @@ import org.springframework.stereotype.Service; /** - * Tests if all the claims in the required set have a matching + * Tests if all the claims in the required set have a matching * value in the supplied set. - * + * * @author jricher * */ @@ -54,7 +53,7 @@ public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket allUnmatched.addAll(unmatched); } } - + // otherwise, tell the caller that we'll need some set of these fulfilled somehow return new ClaimProcessingResult(allUnmatched); } @@ -62,29 +61,29 @@ public ClaimProcessingResult claimsAreSatisfied(ResourceSet rs, PermissionTicket private Collection checkIndividualClaims(Collection claimsRequired, Collection claimsSupplied) { Collection claimsUnmatched = new HashSet<>(claimsRequired); - + // see if each of the required claims has a counterpart in the supplied claims set for (Claim required : claimsRequired) { for (Claim supplied : claimsSupplied) { - + if (required.getIssuer().containsAll(supplied.getIssuer())) { // it's from the right issuer - + if (required.getName().equals(supplied.getName()) && required.getValue().equals(supplied.getValue())) { - + // the claim matched, pull it from the set claimsUnmatched.remove(required); - + } - + } } } // if there's anything left then the claims aren't satisfied, return the leftovers return claimsUnmatched; - + } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/service/impl/UmaDataServiceExtension_1_3.java b/uma-server/src/main/java/org/mitre/uma/service/impl/UmaDataServiceExtension_1_3.java new file mode 100644 index 0000000000..6e9fba180f --- /dev/null +++ b/uma-server/src/main/java/org/mitre/uma/service/impl/UmaDataServiceExtension_1_3.java @@ -0,0 +1,715 @@ +/******************************************************************************* + * 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.impl; + +import static org.mitre.util.JsonUtils.readSet; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.model.RegisteredClient; +import org.mitre.oauth2.repository.OAuth2TokenRepository; +import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; +import org.mitre.openid.connect.service.MITREidDataService; +import org.mitre.openid.connect.service.MITREidDataServiceExtension; +import org.mitre.openid.connect.service.MITREidDataServiceMaps; +import org.mitre.openid.connect.service.impl.MITREidDataServiceSupport; +import org.mitre.uma.model.Claim; +import org.mitre.uma.model.Permission; +import org.mitre.uma.model.PermissionTicket; +import org.mitre.uma.model.Policy; +import org.mitre.uma.model.ResourceSet; +import org.mitre.uma.model.SavedRegisteredClient; +import org.mitre.uma.repository.PermissionRepository; +import org.mitre.uma.repository.ResourceSetRepository; +import org.mitre.uma.service.SavedRegisteredClientService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * @author jricher + * + */ +@Service("umaDataExtension_1_3") +public class UmaDataServiceExtension_1_3 extends MITREidDataServiceSupport implements MITREidDataServiceExtension { + + private static final String THIS_VERSION = MITREidDataService.MITREID_CONNECT_1_3; + + private static final String REGISTERED_CLIENT = "registeredClient"; + private static final String URI = "uri"; + private static final String NAME = "name"; + private static final String TYPE = "type"; + private static final String VALUE = "value"; + private static final String CLIENT_ID = "clientId"; + private static final String EXPIRATION = "expiration"; + private static final String ID = "id"; + private static final String ICON_URI = "iconUri"; + private static final String OWNER = "owner"; + private static final String POLICIES = "policies"; + private static final String SCOPES = "scopes"; + private static final String CLAIMS_REQUIRED = "claimsRequired"; + private static final String ISSUER = "issuer"; + private static final String CLAIM_TOKEN_FORMAT = "claimTokenFormat"; + private static final String CLAIM_TYPE = "claimType"; + private static final String FRIENDLY_NAME = "friendlyName"; + private static final String PERMISSIONS = "permissions"; + private static final String RESOURCE_SET = "resourceSet"; + private static final String PERMISSION_TICKETS = "permissionTickets"; + private static final String PERMISSION = "permission"; + private static final String TICKET = "ticket"; + private static final String CLAIMS_SUPPLIED = "claimsSupplied"; + private static final String SAVED_REGISTERED_CLIENTS = "savedRegisteredClients"; + private static final String RESOURCE_SETS = "resourceSets"; + private static final String TOKEN_PERMISSIONS = "tokenPermissions"; + private static final String TOKEN_ID = "tokenId"; + + private static final Logger logger = LoggerFactory.getLogger(UmaDataServiceExtension_1_3.class); + + + + @Autowired + private SavedRegisteredClientService registeredClientService; + @Autowired + private ResourceSetRepository resourceSetRepository; + @Autowired + private PermissionRepository permissionRepository; + @Autowired + private OAuth2TokenRepository tokenRepository; + + private Map> tokenToPermissionRefs = new HashMap<>(); + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataServiceExtension#supportsVersion(java.lang.String) + */ + @Override + public boolean supportsVersion(String version) { + return THIS_VERSION.equals(version); + + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataServiceExtension#exportExtensionData(com.google.gson.stream.JsonWriter) + */ + @Override + public void exportExtensionData(JsonWriter writer) throws IOException { + writer.name(SAVED_REGISTERED_CLIENTS); + writer.beginArray(); + writeSavedRegisteredClients(writer); + writer.endArray(); + + writer.name(RESOURCE_SETS); + writer.beginArray(); + writeResourceSets(writer); + writer.endArray(); + + writer.name(PERMISSION_TICKETS); + writer.beginArray(); + writePermissionTickets(writer); + writer.endArray(); + + writer.name(TOKEN_PERMISSIONS); + writer.beginArray(); + writeTokenPermissions(writer); + writer.endArray(); + } + + /** + * @param writer + * @throws IOException + */ + private void writeTokenPermissions(JsonWriter writer) throws IOException { + for (OAuth2AccessTokenEntity token : tokenRepository.getAllAccessTokens()) { + if (!token.getPermissions().isEmpty()) { // skip tokens that don't have the permissions structure attached + writer.beginObject(); + writer.name(TOKEN_ID).value(token.getId()); + writer.name(PERMISSIONS); + writer.beginArray(); + for (Permission p : token.getPermissions()) { + writer.beginObject(); + writer.name(RESOURCE_SET).value(p.getResourceSet().getId()); + writer.name(SCOPES); + writer.beginArray(); + for (String s : p.getScopes()) { + writer.value(s); + } + writer.endArray(); + writer.endObject(); + } + writer.endArray(); + + writer.endObject(); + } + } + } + + /** + * @param writer + * @throws IOException + */ + private void writePermissionTickets(JsonWriter writer) throws IOException { + for (PermissionTicket ticket : permissionRepository.getAll()) { + writer.beginObject(); + + writer.name(CLAIMS_SUPPLIED); + writer.beginArray(); + for (Claim claim : ticket.getClaimsSupplied()) { + writer.beginObject(); + + writer.name(ISSUER); + writer.beginArray(); + for (String issuer : claim.getIssuer()) { + writer.value(issuer); + } + writer.endArray(); + writer.name(CLAIM_TOKEN_FORMAT); + writer.beginArray(); + for (String format : claim.getClaimTokenFormat()) { + writer.value(format); + } + writer.endArray(); + writer.name(CLAIM_TYPE).value(claim.getClaimType()); + writer.name(FRIENDLY_NAME).value(claim.getFriendlyName()); + writer.name(NAME).value(claim.getName()); + writer.name(VALUE).value(claim.getValue().toString()); + writer.endObject(); + } + writer.endArray(); + + writer.name(EXPIRATION).value(toUTCString(ticket.getExpiration())); + + writer.name(PERMISSION); + writer.beginObject(); + Permission p = ticket.getPermission(); + writer.name(RESOURCE_SET).value(p.getResourceSet().getId()); + writer.name(SCOPES); + writer.beginArray(); + for (String s : p.getScopes()) { + writer.value(s); + } + writer.endArray(); + writer.endObject(); + + writer.name(TICKET).value(ticket.getTicket()); + + writer.endObject(); + } + + + } + + /** + * @param writer + * @throws IOException + */ + private void writeResourceSets(JsonWriter writer) throws IOException { + for (ResourceSet rs : resourceSetRepository.getAll()) { + writer.beginObject(); + writer.name(ID).value(rs.getId()); + writer.name(CLIENT_ID).value(rs.getClientId()); + writer.name(ICON_URI).value(rs.getIconUri()); + writer.name(NAME).value(rs.getName()); + writer.name(TYPE).value(rs.getType()); + writer.name(URI).value(rs.getUri()); + writer.name(OWNER).value(rs.getOwner()); + writer.name(POLICIES); + writer.beginArray(); + for (Policy policy : rs.getPolicies()) { + writer.beginObject(); + writer.name(NAME).value(policy.getName()); + writer.name(SCOPES); + writer.beginArray(); + for (String scope : policy.getScopes()) { + writer.value(scope); + } + writer.endArray(); + writer.name(CLAIMS_REQUIRED); + writer.beginArray(); + for (Claim claim : policy.getClaimsRequired()) { + writer.beginObject(); + + writer.name(ISSUER); + writer.beginArray(); + for (String issuer : claim.getIssuer()) { + writer.value(issuer); + } + writer.endArray(); + writer.name(CLAIM_TOKEN_FORMAT); + writer.beginArray(); + for (String format : claim.getClaimTokenFormat()) { + writer.value(format); + } + writer.endArray(); + writer.name(CLAIM_TYPE).value(claim.getClaimType()); + writer.name(FRIENDLY_NAME).value(claim.getFriendlyName()); + writer.name(NAME).value(claim.getName()); + writer.name(VALUE).value(claim.getValue().toString()); + writer.endObject(); + } + writer.endArray(); + writer.endObject(); + } + writer.endArray(); + writer.name(SCOPES); + writer.beginArray(); + for (String scope : rs.getScopes()) { + writer.value(scope); + } + writer.endArray(); + writer.endObject(); + logger.debug("Finished writing resource set {}", rs.getId()); + } + + } + + /** + * @param writer + */ + private void writeSavedRegisteredClients(JsonWriter writer) throws IOException { + for (SavedRegisteredClient src : registeredClientService.getAll()) { + writer.beginObject(); + writer.name(ISSUER).value(src.getIssuer()); + writer.name(REGISTERED_CLIENT).value(src.getRegisteredClient().getSource().toString()); + writer.endObject(); + logger.debug("Wrote saved registered client {}", src.getId()); + } + logger.info("Done writing saved registered clients"); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataServiceExtension#importExtensionData(com.google.gson.stream.JsonReader) + */ + @Override + public boolean importExtensionData(String name, JsonReader reader) throws IOException { + if (name.equals(SAVED_REGISTERED_CLIENTS)) { + readSavedRegisteredClients(reader); + return true; + } else if (name.equals(RESOURCE_SETS)) { + readResourceSets(reader); + return true; + } else if (name.equals(PERMISSION_TICKETS)) { + readPermissionTickets(reader); + return true; + } else if (name.equals(TOKEN_PERMISSIONS)) { + readTokenPermissions(reader); + return true; + } else { + return false; + } + } + + /** + * @param reader + */ + private void readTokenPermissions(JsonReader reader) throws IOException { + reader.beginArray(); + while(reader.hasNext()) { + reader.beginObject(); + Long tokenId = null; + Set permissions = new HashSet<>(); + while (reader.hasNext()) { + switch(reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (name.equals(TOKEN_ID)) { + tokenId = reader.nextLong(); + } else if (name.equals(PERMISSIONS)) { + reader.beginArray(); + while (reader.hasNext()) { + Permission p = new Permission(); + Long rsid = null; + Set scope = new HashSet<>(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String pname = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (pname.equals(RESOURCE_SET)) { + rsid = reader.nextLong(); + } else if (pname.equals(SCOPES)) { + scope = readSet(reader); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + p.setScopes(scope); + Permission saved = permissionRepository.saveRawPermission(p); + permissionToResourceRefs.put(saved.getId(), rsid); + permissions.add(saved.getId()); + } + reader.endArray(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + tokenToPermissionRefs.put(tokenId, permissions); + } + reader.endArray(); + + } + + private Map permissionToResourceRefs = new HashMap<>(); + + /** + * @param reader + */ + private void readPermissionTickets(JsonReader reader) throws IOException { + JsonParser parser = new JsonParser(); + reader.beginArray(); + while (reader.hasNext()) { + PermissionTicket ticket = new PermissionTicket(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(CLAIMS_SUPPLIED)) { + Set claimsSupplied = new HashSet<>(); + reader.beginArray(); + while (reader.hasNext()) { + Claim c = new Claim(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String cname = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (cname.equals(ISSUER)) { + c.setIssuer(readSet(reader)); + } else if (cname.equals(CLAIM_TOKEN_FORMAT)) { + c.setClaimTokenFormat(readSet(reader)); + } else if (cname.equals(CLAIM_TYPE)) { + c.setClaimType(reader.nextString()); + } else if (cname.equals(FRIENDLY_NAME)) { + c.setFriendlyName(reader.nextString()); + } else if (cname.equals(NAME)) { + c.setName(reader.nextString()); + } else if (cname.equals(VALUE)) { + JsonElement e = parser.parse(reader.nextString()); + c.setValue(e); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + claimsSupplied.add(c); + } + reader.endArray(); + ticket.setClaimsSupplied(claimsSupplied); + } else if (name.equals(EXPIRATION)) { + ticket.setExpiration(utcToDate(reader.nextString())); + } else if (name.equals(PERMISSION)) { + Permission p = new Permission(); + Long rsid = null; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String pname = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (pname.equals(RESOURCE_SET)) { + rsid = reader.nextLong(); + } else if (pname.equals(SCOPES)) { + p.setScopes(readSet(reader)); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Permission saved = permissionRepository.saveRawPermission(p); + permissionToResourceRefs.put(saved.getId(), rsid); + ticket.setPermission(saved); + } else if (name.equals(TICKET)) { + ticket.setTicket(reader.nextString()); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + permissionRepository.save(ticket); + } + reader.endArray(); + } + + + private Map resourceSetOldToNewIdMap = new HashMap<>(); + + /** + * @param reader + */ + private void readResourceSets(JsonReader reader) throws IOException { + JsonParser parser = new JsonParser(); + reader.beginArray(); + while (reader.hasNext()) { + Long oldId = null; + ResourceSet rs = new ResourceSet(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ID)) { + oldId = reader.nextLong(); + } else if (name.equals(CLIENT_ID)) { + rs.setClientId(reader.nextString()); + } else if (name.equals(ICON_URI)) { + rs.setIconUri(reader.nextString()); + } else if (name.equals(NAME)) { + rs.setName(reader.nextString()); + } else if (name.equals(TYPE)) { + rs.setType(reader.nextString()); + } else if (name.equals(URI)) { + rs.setUri(reader.nextString()); + } else if (name.equals(OWNER)) { + rs.setOwner(reader.nextString()); + } else if (name.equals(POLICIES)) { + Set policies = new HashSet<>(); + reader.beginArray(); + while (reader.hasNext()) { + Policy p = new Policy(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String pname = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (pname.equals(NAME)) { + p.setName(reader.nextString()); + } else if (pname.equals(SCOPES)) { + p.setScopes(readSet(reader)); + } else if (pname.equals(CLAIMS_REQUIRED)) { + Set claimsRequired = new HashSet<>(); + reader.beginArray(); + while (reader.hasNext()) { + Claim c = new Claim(); + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String cname = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (cname.equals(ISSUER)) { + c.setIssuer(readSet(reader)); + } else if (cname.equals(CLAIM_TOKEN_FORMAT)) { + c.setClaimTokenFormat(readSet(reader)); + } else if (cname.equals(CLAIM_TYPE)) { + c.setClaimType(reader.nextString()); + } else if (cname.equals(FRIENDLY_NAME)) { + c.setFriendlyName(reader.nextString()); + } else if (cname.equals(NAME)) { + c.setName(reader.nextString()); + } else if (cname.equals(VALUE)) { + JsonElement e = parser.parse(reader.nextString()); + c.setValue(e); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + claimsRequired.add(c); + } + reader.endArray(); + p.setClaimsRequired(claimsRequired); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + policies.add(p); + } + reader.endArray(); + rs.setPolicies(policies); + } else if (name.equals(SCOPES)) { + rs.setScopes(readSet(reader)); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + Long newId = resourceSetRepository.save(rs).getId(); + resourceSetOldToNewIdMap.put(oldId, newId); + } + reader.endArray(); + logger.info("Done reading resource sets"); + } + + /** + * @param reader + */ + private void readSavedRegisteredClients(JsonReader reader) throws IOException{ + reader.beginArray(); + while (reader.hasNext()) { + String issuer = null; + String clientString = null; + reader.beginObject(); + while (reader.hasNext()) { + switch (reader.peek()) { + case END_OBJECT: + continue; + case NAME: + String name = reader.nextName(); + if (reader.peek() == JsonToken.NULL) { + reader.skipValue(); + } else if (name.equals(ISSUER)) { + issuer = reader.nextString(); + } else if (name.equals(REGISTERED_CLIENT)) { + clientString = reader.nextString(); + } else { + logger.debug("Found unexpected entry"); + reader.skipValue(); + } + break; + default: + logger.debug("Found unexpected entry"); + reader.skipValue(); + continue; + } + } + reader.endObject(); + RegisteredClient client = ClientDetailsEntityJsonProcessor.parseRegistered(clientString); + registeredClientService.save(issuer, client); + logger.debug("Saved registered client"); + } + reader.endArray(); + logger.info("Done reading saved registered clients"); + } + + /* (non-Javadoc) + * @see org.mitre.openid.connect.service.MITREidDataServiceExtension#fixExtensionObjectReferences() + */ + @Override + public void fixExtensionObjectReferences(MITREidDataServiceMaps maps) { + for (Long permissionId : permissionToResourceRefs.keySet()) { + Long oldResourceId = permissionToResourceRefs.get(permissionId); + Long newResourceId = resourceSetOldToNewIdMap.get(oldResourceId); + Permission p = permissionRepository.getById(permissionId); + ResourceSet rs = resourceSetRepository.getById(newResourceId); + p.setResourceSet(rs); + permissionRepository.saveRawPermission(p); + logger.debug("Mapping rsid " + oldResourceId + " to " + newResourceId + " for permission " + permissionId); + } + for (Long tokenId : tokenToPermissionRefs.keySet()) { + Long newTokenId = maps.getAccessTokenOldToNewIdMap().get(tokenId); + OAuth2AccessTokenEntity token = tokenRepository.getAccessTokenById(newTokenId); + + Set permissions = new HashSet<>(); + for (Long permissionId : tokenToPermissionRefs.get(tokenId)) { + Permission p = permissionRepository.getById(permissionId); + permissions.add(p); + } + + token.setPermissions(permissions); + tokenRepository.saveAccessToken(token); + } + permissionToResourceRefs.clear(); + resourceSetOldToNewIdMap.clear(); + tokenToPermissionRefs.clear(); + } + +} diff --git a/uma-server/src/main/java/org/mitre/uma/util/ExternalLoginAuthoritiesMapper.java b/uma-server/src/main/java/org/mitre/uma/util/ExternalLoginAuthoritiesMapper.java index aa2bf52ab5..9626eba04a 100644 --- a/uma-server/src/main/java/org/mitre/uma/util/ExternalLoginAuthoritiesMapper.java +++ b/uma-server/src/main/java/org/mitre/uma/util/ExternalLoginAuthoritiesMapper.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -30,14 +29,14 @@ /** * Utility class to map all external logins to the ROLE_EXTERNAL_USER authority * to prevent them from accessing other parts of the server. - * + * * @author jricher * */ public class ExternalLoginAuthoritiesMapper implements OIDCAuthoritiesMapper { private static final GrantedAuthority ROLE_EXTERNAL_USER = new SimpleGrantedAuthority("ROLE_EXTERNAL_USER"); - + @Override public Collection mapAuthorities(JWT idToken, UserInfo userInfo) { return Sets.newHashSet(ROLE_EXTERNAL_USER); diff --git a/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityAbbreviatedView.java b/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityAbbreviatedView.java index 13df7f1787..9f581fb67c 100644 --- a/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityAbbreviatedView.java +++ b/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityAbbreviatedView.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -51,33 +50,33 @@ public class ResourceSetEntityAbbreviatedView extends AbstractView { public static final String VIEWNAME = "resourceSetEntityAbbreviatedView"; public static final String LOCATION = "location"; - + @Autowired private ConfigurationPropertiesBean config; private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; } - return false; - } - - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .setLongSerializationPolicy(LongSerializationPolicy.STRING) - .create(); + + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { @@ -96,20 +95,20 @@ protected void renderMergedOutputModel(Map model, HttpServletReq if (!Strings.isNullOrEmpty(location)) { response.setHeader(HttpHeaders.LOCATION, location); } - + try { Writer out = response.getWriter(); ResourceSet rs = (ResourceSet) model.get(JsonEntityView.ENTITY); JsonObject o = new JsonObject(); - + o.addProperty("_id", rs.getId().toString()); // set the ID to a string o.addProperty("user_access_policy_uri", config.getIssuer() + "manage/user/policy/" + rs.getId()); - + gson.toJson(o, out); - + } catch (IOException e) { logger.error("IOException in ResourceSetEntityView.java: ", e); diff --git a/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityView.java b/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityView.java index 471b1f6199..e34e474313 100644 --- a/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityView.java +++ b/uma-server/src/main/java/org/mitre/uma/view/ResourceSetEntityView.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -49,33 +48,33 @@ public class ResourceSetEntityView extends AbstractView { private static Logger logger = LoggerFactory.getLogger(JsonEntityView.class); public static final String VIEWNAME = "resourceSetEntityView"; - + @Autowired private ConfigurationPropertiesBean config; private Gson gson = new GsonBuilder() - .setExclusionStrategies(new ExclusionStrategy() { - - @Override - public boolean shouldSkipField(FieldAttributes f) { - - return false; - } - - @Override - public boolean shouldSkipClass(Class clazz) { - // skip the JPA binding wrapper - if (clazz.equals(BeanPropertyBindingResult.class)) { - return true; + .setExclusionStrategies(new ExclusionStrategy() { + + @Override + public boolean shouldSkipField(FieldAttributes f) { + + return false; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + // skip the JPA binding wrapper + if (clazz.equals(BeanPropertyBindingResult.class)) { + return true; + } + return false; } - return false; - } - - }) - .serializeNulls() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") - .setLongSerializationPolicy(LongSerializationPolicy.STRING) - .create(); + + }) + .serializeNulls() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .setLongSerializationPolicy(LongSerializationPolicy.STRING) + .create(); @Override protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) { @@ -94,14 +93,14 @@ protected void renderMergedOutputModel(Map model, HttpServletReq if (!Strings.isNullOrEmpty(location)) { response.setHeader(HttpHeaders.LOCATION, location); } - + try { Writer out = response.getWriter(); ResourceSet rs = (ResourceSet) model.get("entity"); JsonObject o = new JsonObject(); - + o.addProperty("_id", rs.getId().toString()); // send the id as a string o.addProperty("user_access_policy_uri", config.getIssuer() + "manage/resource/" + rs.getId()); o.addProperty("name", rs.getName()); @@ -109,9 +108,9 @@ protected void renderMergedOutputModel(Map model, HttpServletReq o.addProperty("type", rs.getType()); o.add("scopes", JsonUtils.getAsArray(rs.getScopes())); o.addProperty("icon_uri", rs.getIconUri()); - + gson.toJson(o, out); - + } catch (IOException e) { logger.error("IOException in ResourceSetEntityView.java: ", e); diff --git a/uma-server/src/main/java/org/mitre/uma/web/AuthorizationRequestEndpoint.java b/uma-server/src/main/java/org/mitre/uma/web/AuthorizationRequestEndpoint.java index 28ad2d2f31..04f8378445 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/AuthorizationRequestEndpoint.java +++ b/uma-server/src/main/java/org/mitre/uma/web/AuthorizationRequestEndpoint.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -23,7 +22,6 @@ import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.mitre.oauth2.service.SystemScopeService; import org.mitre.oauth2.web.AuthenticationUtilities; -import org.mitre.openid.connect.service.OIDCTokenService; import org.mitre.openid.connect.view.HttpCodeView; import org.mitre.openid.connect.view.JsonEntityView; import org.mitre.openid.connect.view.JsonErrorView; @@ -70,13 +68,10 @@ public class AuthorizationRequestEndpoint { @Autowired private PermissionService permissionService; - + @Autowired private OAuth2TokenEntityService tokenService; - - @Autowired - private OIDCTokenService oidcTokenService; - + @Autowired private ClaimsProcessingService claimsProcessingService; @@ -85,72 +80,72 @@ public class AuthorizationRequestEndpoint { @RequestMapping(method = RequestMethod.POST, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String authorizationRequest(@RequestBody String jsonString, Model m, Authentication auth) { - + AuthenticationUtilities.ensureOAuthScope(auth, SystemScopeService.UMA_AUTHORIZATION_SCOPE); - + JsonParser parser = new JsonParser(); JsonElement e = parser.parse(jsonString); - + if (e.isJsonObject()) { JsonObject o = e.getAsJsonObject(); - + if (o.has(TICKET)) { - + OAuth2AccessTokenEntity incomingRpt = null; if (o.has(RPT)) { String rptValue = o.get(RPT).getAsString(); incomingRpt = tokenService.readAccessToken(rptValue); - } - + } + String ticketValue = o.get(TICKET).getAsString(); - + PermissionTicket ticket = permissionService.getByTicket(ticketValue); - + if (ticket != null) { // found the ticket, see if it's any good - + ResourceSet rs = ticket.getPermission().getResourceSet(); - + if (rs.getPolicies() == null || rs.getPolicies().isEmpty()) { // the required claims are empty, this resource has no way to be authorized - + m.addAttribute(JsonErrorView.ERROR, "not_authorized"); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This resource set can not be accessed."); m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; } else { // claims weren't empty or missing, we need to check against what we have - + ClaimProcessingResult result = claimsProcessingService.claimsAreSatisfied(rs, ticket); - - + + if (result.isSatisfied()) { // the service found what it was looking for, issue a token // we need to downscope this based on the required set that was matched if it was matched OAuth2Authentication o2auth = (OAuth2Authentication) auth; - + OAuth2AccessTokenEntity token = umaTokenService.createRequestingPartyToken(o2auth, ticket, result.getMatched()); // if we have an inbound RPT, throw it out because we're replacing it if (incomingRpt != null) { tokenService.revokeAccessToken(incomingRpt); } - + Map entity = ImmutableMap.of("rpt", token.getValue()); - + m.addAttribute(JsonEntityView.ENTITY, entity); - + return JsonEntityView.VIEWNAME; - + } else { - + // if we got here, the claim didn't match, forward the user to the claim gathering endpoint JsonObject entity = new JsonObject(); - + entity.addProperty(JsonErrorView.ERROR, "need_info"); JsonObject details = new JsonObject(); - + JsonObject rpClaims = new JsonObject(); rpClaims.addProperty("redirect_user", true); rpClaims.addProperty("ticket", ticketValue); @@ -175,12 +170,12 @@ public String authorizationRequest(@RequestBody String jsonString, Model m, Auth rpClaims.add("required_claims", req); details.add("requesting_party_claims", rpClaims); entity.add("error_details", details); - + m.addAttribute(JsonEntityView.ENTITY, entity); return JsonEntityView.VIEWNAME; - } - - + } + + } } else { // ticket wasn't found, return an error @@ -194,14 +189,14 @@ public String authorizationRequest(@RequestBody String jsonString, Model m, Auth m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Missing JSON elements."); return JsonErrorView.VIEWNAME; } - - + + } else { m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Malformed JSON request."); return JsonErrorView.VIEWNAME; } - + } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/web/ClaimsCollectionEndpoint.java b/uma-server/src/main/java/org/mitre/uma/web/ClaimsCollectionEndpoint.java index b34d6e9eb0..52061e4ebf 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/ClaimsCollectionEndpoint.java +++ b/uma-server/src/main/java/org/mitre/uma/web/ClaimsCollectionEndpoint.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -32,6 +31,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.oauth2.common.exceptions.RedirectMismatchException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; @@ -45,9 +45,9 @@ import com.google.gson.JsonPrimitive; /** - * + * * Collect claims interactively from the end user. - * + * * @author jricher * */ @@ -62,35 +62,35 @@ public class ClaimsCollectionEndpoint { @Autowired private ClientDetailsEntityService clientService; - + @Autowired private PermissionService permissionService; - - + + @RequestMapping(method = RequestMethod.GET) - public String collectClaims(@RequestParam("client_id") String clientId, @RequestParam(value = "redirect_uri", required = false) String redirectUri, + public String collectClaims(@RequestParam("client_id") String clientId, @RequestParam(value = "redirect_uri", required = false) String redirectUri, @RequestParam("ticket") String ticketValue, @RequestParam(value = "state", required = false) String state, Model m, OIDCAuthenticationToken auth) { ClientDetailsEntity client = clientService.loadClientByClientId(clientId); - + PermissionTicket ticket = permissionService.getByTicket(ticketValue); - + if (client == null || ticket == null) { logger.info("Client or ticket not found: " + clientId + " :: " + ticketValue); m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + // we've got a client and ticket, let's attach the claims that we have from the token and userinfo - + // subject Set claimsSupplied = Sets.newHashSet(ticket.getClaimsSupplied()); - + String issuer = auth.getIssuer(); UserInfo userInfo = auth.getUserInfo(); - + claimsSupplied.add(mkClaim(issuer, "sub", new JsonPrimitive(auth.getSub()))); if (userInfo.getEmail() != null) { claimsSupplied.add(mkClaim(issuer, "email", new JsonPrimitive(userInfo.getEmail()))); @@ -110,18 +110,24 @@ public String collectClaims(@RequestParam("client_id") String clientId, @Request if (userInfo.getProfile() != null) { claimsSupplied.add(mkClaim(issuer, "profile", new JsonPrimitive(auth.getUserInfo().getProfile()))); } - + ticket.setClaimsSupplied(claimsSupplied); PermissionTicket updatedTicket = permissionService.updateTicket(ticket); if (Strings.isNullOrEmpty(redirectUri)) { - if (client.getRedirectUris().size() == 1) { - redirectUri = client.getRedirectUris().iterator().next(); // get the first (and only) redirect URI to use here + if (client.getClaimsRedirectUris().size() == 1) { + redirectUri = client.getClaimsRedirectUris().iterator().next(); // get the first (and only) redirect URI to use here logger.info("No redirect URI passed in, using registered value: " + redirectUri); + } else { + throw new RedirectMismatchException("Unable to find redirect URI and none passed in."); + } + } else { + if (!client.getClaimsRedirectUris().contains(redirectUri)) { + throw new RedirectMismatchException("Claims redirect did not match the registered values."); } } - + UriComponentsBuilder template = UriComponentsBuilder.fromUriString(redirectUri); template.queryParam("authorization_state", "claims_submitted"); if (!Strings.isNullOrEmpty(state)) { @@ -130,11 +136,11 @@ public String collectClaims(@RequestParam("client_id") String clientId, @Request String uriString = template.toUriString(); logger.info("Redirecting to " + uriString); - + return "redirect:" + uriString; } - + private Claim mkClaim(String issuer, String name, JsonElement value) { Claim c = new Claim(); c.setIssuer(Sets.newHashSet(issuer)); @@ -142,5 +148,5 @@ private Claim mkClaim(String issuer, String name, JsonElement value) { c.setValue(value); return c; } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/web/PermissionRegistrationEndpoint.java b/uma-server/src/main/java/org/mitre/uma/web/PermissionRegistrationEndpoint.java index 456a4add07..a3b6601294 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/PermissionRegistrationEndpoint.java +++ b/uma-server/src/main/java/org/mitre/uma/web/PermissionRegistrationEndpoint.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,9 +16,6 @@ package org.mitre.uma.web; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope; import static org.mitre.util.JsonUtils.getAsLong; import static org.mitre.util.JsonUtils.getAsStringSet; @@ -34,17 +30,15 @@ import org.mitre.uma.model.ResourceSet; import org.mitre.uma.service.PermissionService; import org.mitre.uma.service.ResourceSetService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.common.exceptions.OAuth2Exception; -import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.MimeTypeUtils; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -64,90 +58,90 @@ public class PermissionRegistrationEndpoint { // Logger for this class private static final Logger logger = LoggerFactory.getLogger(PermissionRegistrationEndpoint.class); - + public static final String URL = "permission"; - + @Autowired private PermissionService permissionService; - + @Autowired private ResourceSetService resourceSetService; - + @Autowired private SystemScopeService scopeService; private JsonParser parser = new JsonParser(); - + @RequestMapping(method = RequestMethod.POST, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String getPermissionTicket(@RequestBody String jsonString, Model m, Authentication auth) { - + ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); - + try { - + // parse the permission request - - JsonElement el = parser.parse(jsonString); + + JsonElement el = parser.parse(jsonString); if (el.isJsonObject()) { JsonObject o = el.getAsJsonObject(); - + Long rsid = getAsLong(o, "resource_set_id"); Set scopes = getAsStringSet(o, "scopes"); - + if (rsid == null || scopes == null || scopes.isEmpty()){ // missing information m.addAttribute("code", HttpStatus.BAD_REQUEST); m.addAttribute("errorMessage", "Missing required component of permission registration request."); return JsonErrorView.VIEWNAME; } - + // trim any restricted scopes Set scopesRequested = scopeService.fromStrings(scopes); scopesRequested = scopeService.removeRestrictedAndReservedScopes(scopesRequested); scopes = scopeService.toStrings(scopesRequested); - + ResourceSet resourceSet = resourceSetService.getById(rsid); - + // requested resource set doesn't exist if (resourceSet == null) { m.addAttribute("code", HttpStatus.NOT_FOUND); m.addAttribute("errorMessage", "Requested resource set not found: " + rsid); return JsonErrorView.VIEWNAME; } - + // authorized user of the token doesn't match owner of the resource set if (!resourceSet.getOwner().equals(auth.getName())) { m.addAttribute("code", HttpStatus.FORBIDDEN); m.addAttribute("errorMessage", "Party requesting permission is not owner of resource set, expected " + resourceSet.getOwner() + " got " + auth.getName()); return JsonErrorView.VIEWNAME; } - + // create the permission PermissionTicket permission = permissionService.createTicket(resourceSet, scopes); - + if (permission != null) { // we've created the permission, return the ticket JsonObject out = new JsonObject(); out.addProperty("ticket", permission.getTicket()); m.addAttribute("entity", out); - + m.addAttribute("code", HttpStatus.CREATED); - + return JsonEntityView.VIEWNAME; } else { // there was a failure creating the permission object - + m.addAttribute("code", HttpStatus.INTERNAL_SERVER_ERROR); m.addAttribute("errorMessage", "Unable to save permission and generate ticket."); - + return JsonErrorView.VIEWNAME; } - + } else { // malformed request m.addAttribute("code", HttpStatus.BAD_REQUEST); m.addAttribute("errorMessage", "Malformed JSON request."); - return JsonErrorView.VIEWNAME; + return JsonErrorView.VIEWNAME; } } catch (JsonParseException e) { // malformed request @@ -155,7 +149,7 @@ public String getPermissionTicket(@RequestBody String jsonString, Model m, Authe m.addAttribute("errorMessage", "Malformed JSON request."); return JsonErrorView.VIEWNAME; } - + } } diff --git a/uma-server/src/main/java/org/mitre/uma/web/PolicyAPI.java b/uma-server/src/main/java/org/mitre/uma/web/PolicyAPI.java index 4f3cdcbf1a..2b1feda583 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/PolicyAPI.java +++ b/uma-server/src/main/java/org/mitre/uma/web/PolicyAPI.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -48,7 +47,7 @@ /** * API for managing policies on resource sets. - * + * * @author jricher * */ @@ -56,18 +55,18 @@ @RequestMapping("/" + PolicyAPI.URL) @PreAuthorize("hasRole('ROLE_USER')") public class PolicyAPI { - + // Logger for this class private static final Logger logger = LoggerFactory.getLogger(PolicyAPI.class); - + public static final String URL = RootController.API_URL + "/resourceset"; public static final String POLICYURL = "/policy"; - + private Gson gson = new Gson(); - + @Autowired private ResourceSetService resourceSetService; - + /** * List all resource sets for the current user * @param m @@ -76,14 +75,14 @@ public class PolicyAPI { */ @RequestMapping(value = "", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String getResourceSetsForCurrentUser(Model m, Authentication auth) { - + Collection resourceSets = resourceSetService.getAllForOwner(auth.getName()); - + m.addAttribute(JsonEntityView.ENTITY, resourceSets); - + return JsonEntityView.VIEWNAME; } - + /** * Get the indicated resource set * @param rsid @@ -93,14 +92,14 @@ public String getResourceSetsForCurrentUser(Model m, Authentication auth) { */ @RequestMapping(value = "/{rsid}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String getResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, Authentication auth) { - + ResourceSet rs = resourceSetService.getById(rsid); if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + if (!rs.getOwner().equals(auth.getName())) { logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); @@ -108,12 +107,12 @@ public String getResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return HttpCodeView.VIEWNAME; } - + m.addAttribute(JsonEntityView.ENTITY, rs); - + return JsonEntityView.VIEWNAME; } - + /** * Delete the indicated resource set * @param rsid @@ -123,14 +122,14 @@ public String getResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, */ @RequestMapping(value = "/{rsid}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String deleteResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, Authentication auth) { - + ResourceSet rs = resourceSetService.getById(rsid); if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + if (!rs.getOwner().equals(auth.getName())) { logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); @@ -142,9 +141,9 @@ public String deleteResourceSet(@PathVariable (value = "rsid") Long rsid, Model resourceSetService.remove(rs); m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); return HttpCodeView.VIEWNAME; - + } - + /** * List all the policies for the given resource set * @param rsid @@ -154,14 +153,14 @@ public String deleteResourceSet(@PathVariable (value = "rsid") Long rsid, Model */ @RequestMapping(value = "/{rsid}" + POLICYURL, method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String getPoliciesForResourceSet(@PathVariable (value = "rsid") Long rsid, Model m, Authentication auth) { - + ResourceSet rs = resourceSetService.getById(rsid); if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + if (!rs.getOwner().equals(auth.getName())) { logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); @@ -169,12 +168,12 @@ public String getPoliciesForResourceSet(@PathVariable (value = "rsid") Long rsid m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return HttpCodeView.VIEWNAME; } - + m.addAttribute(JsonEntityView.ENTITY, rs.getPolicies()); - + return JsonEntityView.VIEWNAME; } - + /** * Create a new policy on the given resource set * @param rsid @@ -190,7 +189,7 @@ public String createNewPolicyForResourceSet(@PathVariable (value = "rsid") Long m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + if (!rs.getOwner().equals(auth.getName())) { logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); @@ -200,13 +199,13 @@ public String createNewPolicyForResourceSet(@PathVariable (value = "rsid") Long } Policy p = gson.fromJson(jsonString, Policy.class); - + if (p.getId() != null) { logger.warn("Tried to add a policy with a non-null ID: " + p.getId()); m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); return HttpCodeView.VIEWNAME; } - + for (Claim claim : p.getClaimsRequired()) { if (claim.getId() != null) { logger.warn("Tried to add a policy with a non-null claim ID: " + claim.getId()); @@ -220,7 +219,7 @@ public String createNewPolicyForResourceSet(@PathVariable (value = "rsid") Long // find the new policy object Collection newPolicies = Sets.difference(new HashSet<>(saved.getPolicies()), new HashSet<>(rs.getPolicies())); - + if (newPolicies.size() == 1) { Policy newPolicy = newPolicies.iterator().next(); m.addAttribute(JsonEntityView.ENTITY, newPolicy); @@ -230,9 +229,9 @@ public String createNewPolicyForResourceSet(@PathVariable (value = "rsid") Long m.addAttribute(HttpCodeView.CODE, HttpStatus.INTERNAL_SERVER_ERROR); return HttpCodeView.VIEWNAME; } - + } - + /** * Get a specific policy * @param rsid @@ -243,14 +242,14 @@ public String createNewPolicyForResourceSet(@PathVariable (value = "rsid") Long */ @RequestMapping(value = "/{rsid}" + POLICYURL + "/{pid}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String getPolicy(@PathVariable (value = "rsid") Long rsid, @PathVariable (value = "pid") Long pid, Model m, Authentication auth) { - + ResourceSet rs = resourceSetService.getById(rsid); if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + if (!rs.getOwner().equals(auth.getName())) { logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); @@ -266,12 +265,12 @@ public String getPolicy(@PathVariable (value = "rsid") Long rsid, @PathVariable return JsonEntityView.VIEWNAME; } } - + // if we made it this far, we haven't found it m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + /** * Update a specific policy * @param rsid @@ -285,12 +284,12 @@ public String getPolicy(@PathVariable (value = "rsid") Long rsid, @PathVariable public String setClaimsForResourceSet(@PathVariable (value = "rsid") Long rsid, @PathVariable (value = "pid") Long pid, @RequestBody String jsonString, Model m, Authentication auth) { ResourceSet rs = resourceSetService.getById(rsid); - + if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + if (!rs.getOwner().equals(auth.getName())) { logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); @@ -298,16 +297,16 @@ public String setClaimsForResourceSet(@PathVariable (value = "rsid") Long rsid, m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return HttpCodeView.VIEWNAME; } - + Policy p = gson.fromJson(jsonString, Policy.class); - + if (!pid.equals(p.getId())) { logger.warn("Policy ID mismatch, expected " + pid + " got " + p.getId()); - + m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); return HttpCodeView.VIEWNAME; } - + for (Policy policy : rs.getPolicies()) { if (policy.getId().equals(pid)) { // found it! @@ -325,14 +324,14 @@ public String setClaimsForResourceSet(@PathVariable (value = "rsid") Long rsid, return HttpCodeView.VIEWNAME; } } - + // update the existing object with the new values policy.setClaimsRequired(p.getClaimsRequired()); policy.setName(p.getName()); policy.setScopes(p.getScopes()); - + resourceSetService.update(rs, rs); - + m.addAttribute(JsonEntityView.ENTITY, policy); return JsonEntityView.VIEWNAME; } @@ -342,7 +341,7 @@ public String setClaimsForResourceSet(@PathVariable (value = "rsid") Long rsid, m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; } - + /** * Delete a specific policy * @param rsid @@ -355,38 +354,38 @@ public String setClaimsForResourceSet(@PathVariable (value = "rsid") Long rsid, public String deleteResourceSet(@PathVariable ("rsid") Long rsid, @PathVariable (value = "pid") Long pid, Model m, Authentication auth) { ResourceSet rs = resourceSetService.getById(rsid); - + if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.addAttribute(JsonErrorView.ERROR, "not_found"); return JsonErrorView.VIEWNAME; } - + if (!auth.getName().equals(rs.getOwner())) { - + logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); - + // it wasn't issued to this user m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; } - - + + for (Policy policy : rs.getPolicies()) { if (policy.getId().equals(pid)) { // found it! rs.getPolicies().remove(policy); resourceSetService.update(rs, rs); - + m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); return HttpCodeView.VIEWNAME; } } - + // if we made it this far, we haven't found it m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); return HttpCodeView.VIEWNAME; - + } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/web/ResourceSetRegistrationEndpoint.java b/uma-server/src/main/java/org/mitre/uma/web/ResourceSetRegistrationEndpoint.java index 5e284b0e1c..ce10568fba 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/ResourceSetRegistrationEndpoint.java +++ b/uma-server/src/main/java/org/mitre/uma/web/ResourceSetRegistrationEndpoint.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,6 +16,11 @@ package org.mitre.uma.web; +import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope; +import static org.mitre.util.JsonUtils.getAsLong; +import static org.mitre.util.JsonUtils.getAsString; +import static org.mitre.util.JsonUtils.getAsStringSet; + import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -28,8 +32,6 @@ import org.mitre.openid.connect.view.HttpCodeView; import org.mitre.openid.connect.view.JsonEntityView; import org.mitre.openid.connect.view.JsonErrorView; -import org.mitre.uma.model.Claim; -import org.mitre.uma.model.Policy; import org.mitre.uma.model.ResourceSet; import org.mitre.uma.service.ResourceSetService; import org.mitre.uma.view.ResourceSetEntityAbbreviatedView; @@ -50,17 +52,10 @@ import org.springframework.web.bind.annotation.RequestMethod; 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.JsonParseException; import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; - -import static org.mitre.oauth2.web.AuthenticationUtilities.ensureOAuthScope; -import static org.mitre.util.JsonUtils.getAsLong; -import static org.mitre.util.JsonUtils.getAsString; -import static org.mitre.util.JsonUtils.getAsStringSet; @Controller @RequestMapping("/" + ResourceSetRegistrationEndpoint.URL) @@ -68,13 +63,13 @@ public class ResourceSetRegistrationEndpoint { private static final Logger logger = LoggerFactory.getLogger(ResourceSetRegistrationEndpoint.class); - + public static final String DISCOVERY_URL = "resource_set"; public static final String URL = DISCOVERY_URL + "/resource_set"; @Autowired private ResourceSetService resourceSetService; - + @Autowired private ConfigurationPropertiesBean config; @@ -82,16 +77,16 @@ public class ResourceSetRegistrationEndpoint { private SystemScopeService scopeService; private JsonParser parser = new JsonParser(); - + @RequestMapping(method = RequestMethod.POST, produces = MimeTypeUtils.APPLICATION_JSON_VALUE, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE) public String createResourceSet(@RequestBody String jsonString, Model m, Authentication auth) { ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); - + ResourceSet rs = parseResourceSet(jsonString); - + if (rs == null) { // there was no resource set in the body logger.warn("Resource set registration missing body."); - + m.addAttribute("code", HttpStatus.BAD_REQUEST); m.addAttribute("error_description", "Resource request was missing body."); return JsonErrorView.VIEWNAME; @@ -108,48 +103,48 @@ public String createResourceSet(@RequestBody String jsonString, Model m, Authent m.addAttribute(JsonErrorView.ERROR_MESSAGE, "This call must be made with an OAuth token"); return JsonErrorView.VIEWNAME; } - + rs = validateScopes(rs); - + if (Strings.isNullOrEmpty(rs.getName()) // there was no name (required) || rs.getScopes() == null // there were no scopes (required) - ) { + ) { logger.warn("Resource set registration missing one or more required fields."); - + m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields."); return JsonErrorView.VIEWNAME; } ResourceSet saved = resourceSetService.saveNew(rs); - + m.addAttribute(HttpCodeView.CODE, HttpStatus.CREATED); m.addAttribute(JsonEntityView.ENTITY, saved); - m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + rs.getId()); - + m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + saved.getId()); + return ResourceSetEntityAbbreviatedView.VIEWNAME; - + } @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String readResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) { ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); - + ResourceSet rs = resourceSetService.getById(id); - + if (rs == null) { m.addAttribute("code", HttpStatus.NOT_FOUND); m.addAttribute("error", "not_found"); return JsonErrorView.VIEWNAME; } else { - + rs = validateScopes(rs); - + if (!auth.getName().equals(rs.getOwner())) { - + logger.warn("Unauthorized resource set request from wrong user; expected " + rs.getOwner() + " got " + auth.getName()); - + // it wasn't issued to this user m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; @@ -157,9 +152,9 @@ public String readResourceSet(@PathVariable ("id") Long id, Model m, Authenticat m.addAttribute(JsonEntityView.ENTITY, rs); return ResourceSetEntityView.VIEWNAME; } - + } - + } @RequestMapping(value = "/{id}", method = RequestMethod.PUT, consumes = MimeTypeUtils.APPLICATION_JSON_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) @@ -172,85 +167,85 @@ public String updateResourceSet(@PathVariable ("id") Long id, @RequestBody Strin || Strings.isNullOrEmpty(newRs.getName()) // there was no name (required) || newRs.getScopes() == null // there were no scopes (required) || newRs.getId() == null || !newRs.getId().equals(id) // the IDs didn't match - ) { + ) { logger.warn("Resource set registration missing one or more required fields."); - + m.addAttribute(HttpCodeView.CODE, HttpStatus.BAD_REQUEST); m.addAttribute(JsonErrorView.ERROR_MESSAGE, "Resource request was missing one or more required fields."); return JsonErrorView.VIEWNAME; } ResourceSet rs = resourceSetService.getById(id); - + if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.addAttribute(JsonErrorView.ERROR, "not_found"); return JsonErrorView.VIEWNAME; } else { if (!auth.getName().equals(rs.getOwner())) { - + logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); - + // it wasn't issued to this user m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; } else { - + ResourceSet saved = resourceSetService.update(rs, newRs); - + m.addAttribute(JsonEntityView.ENTITY, saved); m.addAttribute(ResourceSetEntityAbbreviatedView.LOCATION, config.getIssuer() + URL + "/" + rs.getId()); return ResourceSetEntityAbbreviatedView.VIEWNAME; } - + } } - + @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String deleteResourceSet(@PathVariable ("id") Long id, Model m, Authentication auth) { ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); ResourceSet rs = resourceSetService.getById(id); - + if (rs == null) { m.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); m.addAttribute(JsonErrorView.ERROR, "not_found"); return JsonErrorView.VIEWNAME; } else { if (!auth.getName().equals(rs.getOwner())) { - + logger.warn("Unauthorized resource set request from bad user; expected " + rs.getOwner() + " got " + auth.getName()); - + // it wasn't issued to this user m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; - } else if (auth instanceof OAuth2Authentication && + } else if (auth instanceof OAuth2Authentication && !((OAuth2Authentication)auth).getOAuth2Request().getClientId().equals(rs.getClientId())){ - + logger.warn("Unauthorized resource set request from bad client; expected " + rs.getClientId() + " got " + ((OAuth2Authentication)auth).getOAuth2Request().getClientId()); - + // it wasn't issued to this client m.addAttribute(HttpCodeView.CODE, HttpStatus.FORBIDDEN); return JsonErrorView.VIEWNAME; } else { - + // user and client matched resourceSetService.remove(rs); - + m.addAttribute(HttpCodeView.CODE, HttpStatus.NO_CONTENT); return HttpCodeView.VIEWNAME; } - + } } - + @RequestMapping(method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String listResourceSets(Model m, Authentication auth) { ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE); - + String owner = auth.getName(); - + Collection resourceSets = Collections.emptySet(); if (auth instanceof OAuth2Authentication) { // if it's an OAuth mediated call, it's on behalf of a client, so look that up too @@ -260,14 +255,14 @@ public String listResourceSets(Model m, Authentication auth) { // otherwise get everything for the current user resourceSets = resourceSetService.getAllForOwner(owner); } - + // build the entity here and send to the display - + Set ids = new HashSet<>(); for (ResourceSet resourceSet : resourceSets) { ids.add(resourceSet.getId().toString()); // add them all as strings so that gson renders them properly } - + m.addAttribute(JsonEntityView.ENTITY, ids); return JsonEntityView.VIEWNAME; } @@ -276,10 +271,10 @@ private ResourceSet parseResourceSet(String jsonString) { try { JsonElement el = parser.parse(jsonString); - + if (el.isJsonObject()) { JsonObject o = el.getAsJsonObject(); - + ResourceSet rs = new ResourceSet(); rs.setId(getAsLong(o, "_id")); rs.setName(getAsString(o, "name")); @@ -287,24 +282,24 @@ private ResourceSet parseResourceSet(String jsonString) { rs.setType(getAsString(o, "type")); rs.setScopes(getAsStringSet(o, "scopes")); rs.setUri(getAsString(o, "uri")); - + return rs; - + } - + return null; - + } catch (JsonParseException e) { return null; } - + } - + /** - * + * * Make sure the resource set doesn't have any restricted or reserved scopes. - * + * * @param rs */ private ResourceSet validateScopes(ResourceSet rs) { diff --git a/uma-server/src/main/java/org/mitre/uma/web/UmaDiscoveryEndpoint.java b/uma-server/src/main/java/org/mitre/uma/web/UmaDiscoveryEndpoint.java index f11422c662..6dc8717adb 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/UmaDiscoveryEndpoint.java +++ b/uma-server/src/main/java/org/mitre/uma/web/UmaDiscoveryEndpoint.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -39,10 +38,10 @@ */ @Controller public class UmaDiscoveryEndpoint { - + @Autowired private ConfigurationPropertiesBean config; - + @RequestMapping(".well-known/uma-configuration") public String umaConfiguration(Model model) { @@ -69,12 +68,12 @@ public String umaConfiguration(Model model) { m.put("resource_set_registration_endpoint", issuer + ResourceSetRegistrationEndpoint.DISCOVERY_URL); m.put("permission_registration_endpoint", issuer + PermissionRegistrationEndpoint.URL); m.put("rpt_endpoint", issuer + AuthorizationRequestEndpoint.URL); - - - + + + model.addAttribute("entity", m); return JsonEntityView.VIEWNAME; } - + } diff --git a/uma-server/src/main/java/org/mitre/uma/web/UserClaimSearchHelper.java b/uma-server/src/main/java/org/mitre/uma/web/UserClaimSearchHelper.java index 7221a54325..3773264707 100644 --- a/uma-server/src/main/java/org/mitre/uma/web/UserClaimSearchHelper.java +++ b/uma-server/src/main/java/org/mitre/uma/web/UserClaimSearchHelper.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -55,22 +54,22 @@ public class UserClaimSearchHelper { public static final String URL = RootController.API_URL + "/emailsearch"; - + private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService(); - + @Autowired private UserInfoService userInfoService; - + @Autowired private ConfigurationPropertiesBean config; - + @RequestMapping(method = RequestMethod.GET, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public String search(@RequestParam(value = "identifier") String email, Model m, Authentication auth, HttpServletRequest req) { - + // check locally first UserInfo localUser = userInfoService.getByEmailAddress(email); - + if (localUser != null) { Map e = new HashMap<>(); e.put("issuer", ImmutableSet.of(config.getIssuer())); @@ -90,22 +89,22 @@ public String search(@RequestParam(value = "identifier") String email, Model m, m.addAttribute(JsonEntityView.ENTITY, ImmutableSet.of(e, ev, s)); return JsonEntityView.VIEWNAME; } else { - + // otherwise do a webfinger lookup IssuerServiceResponse resp = webfingerIssuerService.getIssuer(req); - + if (resp != null && resp.getIssuer() != null) { // we found an issuer, return that Map e = new HashMap<>(); e.put("issuer", ImmutableSet.of(resp.getIssuer())); e.put("name", "email"); e.put("value", email); - + Map ev = new HashMap<>(); ev.put("issuer", ImmutableSet.of(resp.getIssuer())); ev.put("name", "email_verified"); ev.put("value", true); - + m.addAttribute(JsonEntityView.ENTITY, ImmutableSet.of(e, ev)); return JsonEntityView.VIEWNAME; } else { @@ -114,5 +113,5 @@ public String search(@RequestParam(value = "identifier") String email, Model m, } } } - + } diff --git a/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultPermissionService.java b/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultPermissionService.java index 01a06e8d91..0a2063cb3e 100644 --- a/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultPermissionService.java +++ b/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultPermissionService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,6 +16,8 @@ package org.mitre.uma.service.impl; +import static org.mockito.Matchers.anySetOf; + import java.util.Set; import java.util.UUID; @@ -29,8 +30,8 @@ import org.mitre.uma.repository.PermissionRepository; import org.mockito.AdditionalAnswers; import org.mockito.InjectMocks; +import org.mockito.Matchers; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; @@ -41,8 +42,6 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.not; -import static org.mockito.Matchers.anySetOf; - import static org.mockito.Mockito.when; import static org.junit.Assert.assertNotNull; @@ -57,16 +56,16 @@ public class TestDefaultPermissionService { @Mock private PermissionRepository permissionRepository; - + @Mock private SystemScopeService scopeService; - + @InjectMocks private DefaultPermissionService permissionService; - + private Set scopes1 = ImmutableSet.of("foo", "bar", "baz"); private Set scopes2 = ImmutableSet.of("alpha", "beta", "betest"); - + private ResourceSet rs1; private ResourceSet rs2; @@ -77,8 +76,8 @@ public class TestDefaultPermissionService { private String rs2Name = "resource set 2"; private String rs2Owner = "resource set owner 2"; private Long rs2Id = 2L; - - + + @Before public void prepare() { rs1 = new ResourceSet(); @@ -86,7 +85,7 @@ public void prepare() { rs1.setOwner(rs1Owner); rs1.setId(rs1Id ); rs1.setScopes(scopes1); - + rs2 = new ResourceSet(); rs2.setName(rs2Name); rs2.setOwner(rs2Owner); @@ -94,8 +93,8 @@ public void prepare() { rs2.setScopes(scopes2); // have the repository just pass the argument through - when(permissionRepository.save(Mockito.any(PermissionTicket.class))).then(AdditionalAnswers.returnsFirstArg()); - + when(permissionRepository.save(Matchers.any(PermissionTicket.class))).then(AdditionalAnswers.returnsFirstArg()); + when(scopeService.scopesMatch(anySetOf(String.class), anySetOf(String.class))).then(new Answer() { @Override @@ -105,65 +104,65 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable { Set expected = (Set) arguments[0]; @SuppressWarnings("unchecked") Set actual = (Set) arguments[1]; - + return expected.containsAll(actual); } }); - + } - - + + /** * Test method for {@link org.mitre.uma.service.impl.DefaultPermissionService#createTicket(org.mitre.uma.model.ResourceSet, java.util.Set)}. */ @Test public void testCreate_ticket() { - + PermissionTicket perm = permissionService.createTicket(rs1, scopes1); - + // we want there to be a non-null ticket assertNotNull(perm.getTicket()); } - + @Test public void testCreate_uuid() { PermissionTicket perm = permissionService.createTicket(rs1, scopes1); // we expect this to be a UUID UUID uuid = UUID.fromString(perm.getTicket()); - + assertNotNull(uuid); - + } - + @Test public void testCreate_differentTicketsSameClient() { - + PermissionTicket perm1 = permissionService.createTicket(rs1, scopes1); PermissionTicket perm2 = permissionService.createTicket(rs1, scopes1); - + assertNotNull(perm1.getTicket()); assertNotNull(perm2.getTicket()); // make sure these are different from each other assertThat(perm1.getTicket(), not(equalTo(perm2.getTicket()))); - + } - + @Test public void testCreate_differentTicketsDifferentClient() { - + PermissionTicket perm1 = permissionService.createTicket(rs1, scopes1); PermissionTicket perm2 = permissionService.createTicket(rs2, scopes2); - + assertNotNull(perm1.getTicket()); assertNotNull(perm2.getTicket()); // make sure these are different from each other assertThat(perm1.getTicket(), not(equalTo(perm2.getTicket()))); - + } - + @Test(expected = InsufficientScopeException.class) public void testCreate_scopeMismatch() { @SuppressWarnings("unused") diff --git a/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultResourceSetService.java b/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultResourceSetService.java index 0a5445acaf..52ca091be8 100644 --- a/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultResourceSetService.java +++ b/uma-server/src/test/java/org/mitre/uma/service/impl/TestDefaultResourceSetService.java @@ -1,6 +1,5 @@ /******************************************************************************* - * Copyright 2015 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. @@ -17,6 +16,8 @@ package org.mitre.uma.service.impl; +import static org.mockito.Matchers.any; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -27,8 +28,6 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import static org.mockito.Matchers.any; - import static org.mockito.Mockito.when; /** @@ -40,18 +39,18 @@ public class TestDefaultResourceSetService { @Mock private ResourceSetRepository repository; - + @InjectMocks private DefaultResourceSetService resourceSetService; - + /** * @throws java.lang.Exception */ @Before public void setUp() throws Exception { - + when(repository.save(any(ResourceSet.class))).then(AdditionalAnswers.returnsFirstArg()); - + } /** @@ -59,34 +58,34 @@ public void setUp() throws Exception { */ @Test(expected = IllegalArgumentException.class) public void testSaveNew_hasId() { - + ResourceSet rs = new ResourceSet(); rs.setId(1L); - + resourceSetService.saveNew(rs); - + } - + @Test(expected = IllegalArgumentException.class) public void testUpdate_nullId() { ResourceSet rs = new ResourceSet(); rs.setId(1L); ResourceSet rs2 = new ResourceSet(); - + resourceSetService.update(rs, rs2); } - + @Test(expected = IllegalArgumentException.class) public void testUpdate_nullId2() { ResourceSet rs = new ResourceSet(); ResourceSet rs2 = new ResourceSet(); rs2.setId(1L); - + resourceSetService.update(rs, rs2); } - + @Test(expected = IllegalArgumentException.class) public void testUpdate_mismatchedIds() { ResourceSet rs = new ResourceSet(); @@ -94,7 +93,7 @@ public void testUpdate_mismatchedIds() { ResourceSet rs2 = new ResourceSet(); rs2.setId(2L); - + resourceSetService.update(rs, rs2); }