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 new file mode 100644 index 000000000..fc259977f --- /dev/null +++ b/openid-connect-common/src/main/java/org/mitre/openid/connect/service/OIDCTokenService.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright 2013 The MITRE Corporation + * and the MIT Kerberos and Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.mitre.openid.connect.service; + +import java.util.Date; + +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.springframework.security.oauth2.provider.OAuth2Request; + +import com.nimbusds.jose.JWSAlgorithm; + +/** + * Service to create specialty OpenID Connect tokens. + * + * @author Amanda Anganes + * + */ +public interface OIDCTokenService { + + /** + * Create an id token with the information provided. + * + * @param client + * @param request + * @param issueTime + * @param sub + * @param signingAlg + * @param accessToken + * @return + */ + public OAuth2AccessTokenEntity createIdToken( + ClientDetailsEntity client, OAuth2Request request, Date issueTime, + String sub, JWSAlgorithm signingAlg, + OAuth2AccessTokenEntity accessToken); + + /** + * Create a registration access token for the given client. + * + * @param client + * @return + */ + public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client); + +} \ No newline at end of file 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 new file mode 100644 index 000000000..fbfb15c16 --- /dev/null +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/service/impl/DefaultOIDCTokenService.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright 2013 The MITRE Corporation + * and the MIT Kerberos and Internet Trust Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.mitre.openid.connect.service.impl; + +import java.util.Date; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.mitre.jwt.signer.service.JwtSigningAndValidationService; +import org.mitre.oauth2.model.AuthenticationHolderEntity; +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.OAuth2AccessTokenEntity; +import org.mitre.oauth2.repository.AuthenticationHolderRepository; +import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.service.OIDCTokenService; +import org.mitre.openid.connect.util.IdTokenHashUtils; +import org.mitre.openid.connect.web.AuthenticationTimeStamper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.common.util.OAuth2Utils; +import org.springframework.security.oauth2.provider.OAuth2Authentication; +import org.springframework.security.oauth2.provider.OAuth2Request; +import org.springframework.stereotype.Service; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.util.Base64URL; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +/** + * Default implementation of service to create specialty OpenID Connect tokens. + * + * @author Amanda Anganes + * + */ +@Service +public class DefaultOIDCTokenService implements OIDCTokenService { + + Logger logger = LoggerFactory.getLogger(DefaultOIDCTokenService.class); + + @Autowired + private JwtSigningAndValidationService jwtService; + + @Autowired + private AuthenticationHolderRepository authenticationHolderRepository; + + @Autowired + private ConfigurationPropertiesBean configBean; + + @Override + public OAuth2AccessTokenEntity createIdToken(ClientDetailsEntity client, OAuth2Request request, Date issueTime, String sub, JWSAlgorithm signingAlg, OAuth2AccessTokenEntity accessToken) { + + OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity(); + JWTClaimsSet idClaims = new JWTClaimsSet(); + + if (request.getExtensions().containsKey(AuthenticationTimeStamper.AUTH_TIMESTAMP)) { + Date authTime = (Date) request.getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP); + idClaims.setClaim("auth_time", authTime.getTime() / 1000); + } + + idClaims.setIssueTime(issueTime); + + if (client.getIdTokenValiditySeconds() != null) { + Date expiration = new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); + idClaims.setExpirationTime(expiration); + idTokenEntity.setExpiration(expiration); + } + + idClaims.setIssuer(configBean.getIssuer()); + idClaims.setSubject(sub); + idClaims.setAudience(Lists.newArrayList(client.getClientId())); + + String nonce = (String)request.getExtensions().get("nonce"); + if (!Strings.isNullOrEmpty(nonce)) { + idClaims.setCustomClaim("nonce", nonce); + } + + // TODO: this ought to be getResponseType; issue #482 + String responseType = request.getRequestParameters().get("response_type"); + + Set responseTypes = OAuth2Utils.parseParameterList(responseType); + if (responseTypes.contains("token")) { + // calculate the token hash + Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, accessToken); + idClaims.setClaim("at_hash", at_hash); + } + + SignedJWT idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims); + + jwtService.signJwt(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(OAuth2AccessTokenEntity.ID_TOKEN_SCOPE); + idTokenEntity.setScope(idScopes); + + idTokenEntity.setClient(accessToken.getClient()); + + return idTokenEntity; + } + + /** + * @param client + * @return + * @throws AuthenticationException + */ + public OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) { + + Map authorizationParameters = Maps.newHashMap(); + OAuth2Request clientAuth = new OAuth2Request(authorizationParameters, client.getClientId(), + Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")), true, + Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE), null, null, null); + OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null); + + OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); + token.setClient(client); + token.setScope(Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE)); + + AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); + authHolder.setAuthentication(authentication); + 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 + + // TODO: use client's default signing algorithm + JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); + SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims); + + jwtService.signJwt(signed); + + token.setJwt(signed); + + return token; + + } + + /** + * @return the configBean + */ + public ConfigurationPropertiesBean getConfigBean() { + return configBean; + } + + /** + * @param configBean the configBean to set + */ + public void setConfigBean(ConfigurationPropertiesBean configBean) { + this.configBean = configBean; + } + + /** + * @return the jwtService + */ + public JwtSigningAndValidationService getJwtService() { + return jwtService; + } + + /** + * @param jwtService the jwtService to set + */ + public void setJwtService(JwtSigningAndValidationService jwtService) { + this.jwtService = jwtService; + } + + /** + * @return the authenticationHolderRepository + */ + public AuthenticationHolderRepository getAuthenticationHolderRepository() { + return authenticationHolderRepository; + } + + /** + * @param authenticationHolderRepository the authenticationHolderRepository to set + */ + public void setAuthenticationHolderRepository( + AuthenticationHolderRepository authenticationHolderRepository) { + this.authenticationHolderRepository = authenticationHolderRepository; + } + +} 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 3d232d379..878ee9db7 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 @@ -17,7 +17,6 @@ package org.mitre.openid.connect.token; import java.util.Date; -import java.util.Set; import java.util.UUID; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; @@ -27,25 +26,20 @@ 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.ApprovedSiteService; +import org.mitre.openid.connect.service.OIDCTokenService; import org.mitre.openid.connect.service.UserInfoService; -import org.mitre.openid.connect.util.IdTokenHashUtils; -import org.mitre.openid.connect.web.AuthenticationTimeStamper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.oauth2.common.OAuth2AccessToken; -import org.springframework.security.oauth2.common.util.OAuth2Utils; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; 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.google.common.collect.Sets; import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.SignedJWT; @@ -68,6 +62,9 @@ public class ConnectTokenEnhancer implements TokenEnhancer { @Autowired private UserInfoService userInfoService; + + @Autowired + private OIDCTokenService connectTokenService; @Override public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) { @@ -109,64 +106,12 @@ public class ConnectTokenEnhancer implements TokenEnhancer { */ if (originalAuthRequest.getScope().contains("openid")) { - // TODO: maybe id tokens need a service layer - String username = authentication.getName(); UserInfo userInfo = userInfoService.getByUsernameAndClientId(username, clientId); - - OAuth2AccessTokenEntity idTokenEntity = new OAuth2AccessTokenEntity(); - - // FIXME: extend the "claims" section for id tokens - JWTClaimsSet idClaims = new JWTClaimsSet(); - - - if (authentication.getOAuth2Request().getExtensions().containsKey(AuthenticationTimeStamper.AUTH_TIMESTAMP)) { - Date authTime = (Date) authentication.getOAuth2Request().getExtensions().get(AuthenticationTimeStamper.AUTH_TIMESTAMP); - idClaims.setClaim("auth_time", authTime.getTime() / 1000); - } - idClaims.setIssueTime(claims.getIssueTime()); - - if (client.getIdTokenValiditySeconds() != null) { - Date expiration = new Date(System.currentTimeMillis() + (client.getIdTokenValiditySeconds() * 1000L)); - idClaims.setExpirationTime(expiration); - idTokenEntity.setExpiration(expiration); - } - - idClaims.setIssuer(configBean.getIssuer()); - idClaims.setSubject(userInfo.getSub()); - idClaims.setAudience(Lists.newArrayList(clientId)); - - String nonce = (String)originalAuthRequest.getExtensions().get("nonce"); - if (!Strings.isNullOrEmpty(nonce)) { - idClaims.setCustomClaim("nonce", nonce); - } - - // TODO: this ought to be getResponseType; issue #482 - String responseType = authentication.getOAuth2Request().getRequestParameters().get("response_type"); - - Set responseTypes = OAuth2Utils.parseParameterList(responseType); - if (responseTypes.contains("token")) { - // calculate the token hash - Base64URL at_hash = IdTokenHashUtils.getAccessTokenHash(signingAlg, token); - //TODO: What should happen if the hash cannot be calculated? - idClaims.setClaim("at_hash", at_hash); - } - - SignedJWT idToken = new SignedJWT(new JWSHeader(signingAlg), idClaims); - - jwtService.signJwt(idToken); - - idTokenEntity.setJwt(idToken); - - idTokenEntity.setAuthenticationHolder(token.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(OAuth2AccessTokenEntity.ID_TOKEN_SCOPE); - idTokenEntity.setScope(idScopes); - - idTokenEntity.setClient(token.getClient()); + OAuth2AccessTokenEntity idTokenEntity = connectTokenService.createIdToken(client, + originalAuthRequest, (java.util.Date) claims.getIssueTime(), + userInfo.getSub(), signingAlg, token); // attach the id token to the parent access token token.setIdToken(idTokenEntity); diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java index 0b2ece379..f7b5f8d4a 100644 --- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java +++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/ClientDynamicRegistrationEndpoint.java @@ -16,34 +16,27 @@ ******************************************************************************/ package org.mitre.openid.connect.web; -import java.util.Date; -import java.util.Map; import java.util.Set; -import java.util.UUID; import java.util.concurrent.TimeUnit; import org.mitre.jwt.signer.service.JwtSigningAndValidationService; -import org.mitre.oauth2.model.AuthenticationHolderEntity; import org.mitre.oauth2.model.ClientDetailsEntity; import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.OAuth2AccessTokenEntity; import org.mitre.oauth2.model.RegisteredClient; import org.mitre.oauth2.model.SystemScope; -import org.mitre.oauth2.repository.AuthenticationHolderRepository; import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.OAuth2TokenEntityService; import org.mitre.oauth2.service.SystemScopeService; import org.mitre.openid.connect.ClientDetailsEntityJsonProcessor; import org.mitre.openid.connect.config.ConfigurationPropertiesBean; +import org.mitre.openid.connect.service.OIDCTokenService; 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.AuthenticationException; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.provider.OAuth2Authentication; -import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -52,13 +45,7 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; @Controller @RequestMapping(value = "register") @@ -72,18 +59,15 @@ public class ClientDynamicRegistrationEndpoint { @Autowired private JwtSigningAndValidationService jwtService; - - @Autowired - private ConfigurationPropertiesBean configBean; - - @Autowired - private AuthenticationHolderRepository authenticationHolderRepository; @Autowired private SystemScopeService scopeService; @Autowired private ConfigurationPropertiesBean config; + + @Autowired + private OIDCTokenService connectTokenService; private static Logger logger = LoggerFactory.getLogger(ClientDynamicRegistrationEndpoint.class); @@ -170,7 +154,8 @@ public class ClientDynamicRegistrationEndpoint { ClientDetailsEntity savedClient = clientService.saveNewClient(newClient); // generate the registration access token - OAuth2AccessTokenEntity token = createRegistrationAccessToken(savedClient); + OAuth2AccessTokenEntity token = connectTokenService.createRegistrationAccessToken(savedClient); + tokenService.saveAccessToken(token); // send it all out to the view @@ -345,50 +330,4 @@ public class ClientDynamicRegistrationEndpoint { } } - - - /** - * @param client - * @return - * @throws AuthenticationException - */ - private OAuth2AccessTokenEntity createRegistrationAccessToken(ClientDetailsEntity client) throws AuthenticationException { - - Map authorizationParameters = Maps.newHashMap(); - OAuth2Request clientAuth = new OAuth2Request(authorizationParameters, client.getClientId(), - Sets.newHashSet(new SimpleGrantedAuthority("ROLE_CLIENT")), true, - Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE), null, null, null); - OAuth2Authentication authentication = new OAuth2Authentication(clientAuth, null); - - OAuth2AccessTokenEntity token = new OAuth2AccessTokenEntity(); - token.setClient(client); - token.setScope(Sets.newHashSet(OAuth2AccessTokenEntity.REGISTRATION_TOKEN_SCOPE)); - - AuthenticationHolderEntity authHolder = new AuthenticationHolderEntity(); - authHolder.setAuthentication(authentication); - 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 - - // TODO: use client's default signing algorithm - JWSAlgorithm signingAlg = jwtService.getDefaultSigningAlgorithm(); - SignedJWT signed = new SignedJWT(new JWSHeader(signingAlg), claims); - - jwtService.signJwt(signed); - - token.setJwt(signed); - - tokenService.saveAccessToken(token); - - return token; - - } - }