Repository: syncope Updated Branches: refs/heads/2_0_X a2c5c100d -> 381150dd8
Fixing issue with getting bytes for jwtKey + adding JWT tests Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/381150dd Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/381150dd Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/381150dd Branch: refs/heads/2_0_X Commit: 381150dd8fb50f3f546f7b18e86db636ec8e00b0 Parents: a2c5c10 Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Thu Jun 15 12:05:54 2017 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Thu Jun 15 12:06:44 2017 +0100 ---------------------------------------------------------------------- .../src/main/resources/securityContext.xml | 4 +- .../org/apache/syncope/fit/AbstractITCase.java | 9 + .../org/apache/syncope/fit/core/JWTITCase.java | 303 +++++++++++++++++++ 3 files changed, 314 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/381150dd/core/spring/src/main/resources/securityContext.xml ---------------------------------------------------------------------- diff --git a/core/spring/src/main/resources/securityContext.xml b/core/spring/src/main/resources/securityContext.xml index 809b78d..2705b42 100644 --- a/core/spring/src/main/resources/securityContext.xml +++ b/core/spring/src/main/resources/securityContext.xml @@ -49,13 +49,13 @@ under the License. <constructor-arg value="${jwsKey}"/> </bean> <bean id="jwsSignatureVerifier" class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier"> - <constructor-arg value="${jwsKey}.bytes" index="0"/> + <constructor-arg value="#{jwsKey.getBytes()}" index="0"/> <constructor-arg index="1"> <value type="org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm">HS512</value> </constructor-arg> </bean> <bean id="jwsSignatureProvider" class="org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider"> - <constructor-arg value="${jwsKey}.bytes" index="0"/> + <constructor-arg value="#{jwsKey.getBytes()}" index="0"/> <constructor-arg index="1"> <value type="org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm">HS512</value> </constructor-arg> http://git-wip-us.apache.org/repos/asf/syncope/blob/381150dd/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java index f22a045..d39ca3c 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java @@ -59,6 +59,7 @@ import org.apache.syncope.common.lib.types.ConnConfProperty; import org.apache.syncope.common.lib.types.PatchOperation; import org.apache.syncope.common.lib.types.SchemaType; import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.service.AccessTokenService; import org.apache.syncope.common.rest.api.service.AnyObjectService; import org.apache.syncope.common.rest.api.service.AnyTypeClassService; import org.apache.syncope.common.rest.api.service.AnyTypeService; @@ -163,6 +164,10 @@ public abstract class AbstractITCase { protected static String ANONYMOUS_KEY; + protected static String JWS_KEY; + + protected static String JWT_ISSUER; + protected static SyncopeClientFactoryBean clientFactory; protected static SyncopeClient adminClient; @@ -235,6 +240,8 @@ public abstract class AbstractITCase { ANONYMOUS_UNAME = props.getProperty("anonymousUser"); ANONYMOUS_KEY = props.getProperty("anonymousKey"); + JWS_KEY = props.getProperty("jwsKey"); + JWT_ISSUER = props.getProperty("jwtIssuer"); } catch (Exception e) { LOG.error("Could not read secretKey", e); } finally { @@ -243,6 +250,8 @@ public abstract class AbstractITCase { assertNotNull(ANONYMOUS_UNAME); assertNotNull(ANONYMOUS_KEY); + assertNotNull(JWS_KEY); + assertNotNull(JWT_ISSUER); } @BeforeClass http://git-wip-us.apache.org/repos/asf/syncope/blob/381150dd/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java ---------------------------------------------------------------------- diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java new file mode 100644 index 0000000..722b8b7 --- /dev/null +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/JWTITCase.java @@ -0,0 +1,303 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.apache.syncope.fit.core; + +import static org.junit.Assert.*; + +import java.security.AccessControlException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +import javax.ws.rs.core.Response; +import javax.xml.ws.WebServiceException; + +import org.apache.cxf.rs.security.jose.common.JoseType; +import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm; +import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider; +import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier; +import org.apache.cxf.rs.security.jose.jws.JwsHeaders; +import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer; +import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer; +import org.apache.cxf.rs.security.jose.jws.JwsSignatureProvider; +import org.apache.cxf.rs.security.jose.jws.JwsSignatureVerifier; +import org.apache.cxf.rs.security.jose.jwt.JwtClaims; +import org.apache.cxf.rs.security.jose.jwt.JwtToken; +import org.apache.syncope.client.lib.SyncopeClient; +import org.apache.syncope.common.rest.api.RESTHeaders; +import org.apache.syncope.common.rest.api.service.AccessTokenService; +import org.apache.syncope.common.rest.api.service.UserSelfService; +import org.apache.syncope.fit.AbstractITCase; +import org.junit.Test; + +/** + * Some tests for JWT Tokens + */ +public class JWTITCase extends AbstractITCase { + + @Test + public void testGetJWTToken() throws ParseException { + // Get the token + SyncopeClient adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD); + AccessTokenService accessTokenService = adminClient.getService(AccessTokenService.class); + + Response response = accessTokenService.login(); + String token = response.getHeaderString(RESTHeaders.TOKEN); + assertNotNull(token); + String expiry = response.getHeaderString(RESTHeaders.TOKEN_EXPIRE); + assertNotNull(expiry); + + // Validate the signature + JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(token); + JwsSignatureVerifier jwsSignatureVerifier = + new HmacJwsSignatureVerifier(JWS_KEY.getBytes(), SignatureAlgorithm.HS512); + assertTrue(consumer.verifySignatureWith(jwsSignatureVerifier)); + + Date now = new Date(); + + // Verify the expiry header matches that of the token + Long expiryTime = consumer.getJwtClaims().getExpiryTime(); + assertNotNull(expiryTime); + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + Date tokenDate = dateFormat.parse(dateFormat.format(new Date(expiryTime.longValue()))); + Date parsedDate = dateFormat.parse(expiry); + + assertEquals(tokenDate, parsedDate); + assertTrue(parsedDate.after(now)); + + // Verify issuedAt + Long issuedAt = consumer.getJwtClaims().getIssuedAt(); + assertNotNull(issuedAt); + assertTrue(new Date(issuedAt.longValue()).before(now)); + + // Validate subject + issuer + assertEquals("admin", consumer.getJwtClaims().getSubject()); + assertEquals(JWT_ISSUER, consumer.getJwtClaims().getIssuer()); + + // Verify NotBefore + Long notBefore = consumer.getJwtClaims().getNotBefore(); + assertNotNull(notBefore); + assertTrue(new Date(notBefore.longValue()).before(now)); + } + + @Test + public void testQueryUsingToken() throws ParseException { + // Get the token + SyncopeClient adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD); + AccessTokenService accessTokenService = adminClient.getService(AccessTokenService.class); + + Response response = accessTokenService.login(); + String token = response.getHeaderString(RESTHeaders.TOKEN); + assertNotNull(token); + + // Query the UserSelfService using the token + SyncopeClient jwtClient = clientFactory.create(token); + UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class); + jwtUserSelfService.read(); + + // Test a "bad" token + jwtClient = clientFactory.create(token + "xyz"); + jwtUserSelfService = jwtClient.getService(UserSelfService.class); + try { + jwtUserSelfService.read(); + fail("Failure expected on a modified token"); + } catch (WebServiceException ex) { + // expected + } + } + + @Test + public void testTokenValidation() throws ParseException { + // Get an initial token + SyncopeClient adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD); + AccessTokenService accessTokenService = adminClient.getService(AccessTokenService.class); + + Response response = accessTokenService.login(); + String token = response.getHeaderString(RESTHeaders.TOKEN); + assertNotNull(token); + JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(token); + String tokenId = consumer.getJwtClaims().getTokenId(); + + // Create a new token using the Id of the first token + Date now = new Date(); + + Calendar expiry = Calendar.getInstance(); + expiry.setTime(now); + expiry.add(Calendar.MINUTE, 5); + + JwtClaims jwtClaims = new JwtClaims(); + jwtClaims.setTokenId(tokenId); + jwtClaims.setSubject("admin"); + jwtClaims.setIssuedAt(now.getTime()); + jwtClaims.setIssuer(JWT_ISSUER); + jwtClaims.setExpiryTime(expiry.getTime().getTime()); + jwtClaims.setNotBefore(now.getTime()); + + JwsHeaders jwsHeaders = new JwsHeaders(JoseType.JWT, SignatureAlgorithm.HS512); + JwtToken jwtToken = new JwtToken(jwsHeaders, jwtClaims); + JwsJwtCompactProducer producer = new JwsJwtCompactProducer(jwtToken); + + JwsSignatureProvider jwsSignatureProvider = + new HmacJwsSignatureProvider(JWS_KEY.getBytes(), SignatureAlgorithm.HS512); + String signed = producer.signWith(jwsSignatureProvider); + + SyncopeClient jwtClient = clientFactory.create(signed); + UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class); + jwtUserSelfService.read(); + } + + @Test + public void testInvalidIssuer() throws ParseException { + // Get an initial token + SyncopeClient adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD); + AccessTokenService accessTokenService = adminClient.getService(AccessTokenService.class); + + Response response = accessTokenService.login(); + String token = response.getHeaderString(RESTHeaders.TOKEN); + assertNotNull(token); + JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(token); + String tokenId = consumer.getJwtClaims().getTokenId(); + + // Create a new token using the Id of the first token + Date now = new Date(); + + Calendar expiry = Calendar.getInstance(); + expiry.setTime(now); + expiry.add(Calendar.MINUTE, 5); + + JwtClaims jwtClaims = new JwtClaims(); + jwtClaims.setTokenId(tokenId); + jwtClaims.setSubject("admin"); + jwtClaims.setIssuedAt(now.getTime()); + jwtClaims.setIssuer("UnknownIssuer"); + jwtClaims.setExpiryTime(expiry.getTime().getTime()); + jwtClaims.setNotBefore(now.getTime()); + + JwsHeaders jwsHeaders = new JwsHeaders(JoseType.JWT, SignatureAlgorithm.HS512); + JwtToken jwtToken = new JwtToken(jwsHeaders, jwtClaims); + JwsJwtCompactProducer producer = new JwsJwtCompactProducer(jwtToken); + + JwsSignatureProvider jwsSignatureProvider = + new HmacJwsSignatureProvider(JWS_KEY.getBytes(), SignatureAlgorithm.HS512); + String signed = producer.signWith(jwsSignatureProvider); + + SyncopeClient jwtClient = clientFactory.create(signed); + UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class); + try { + jwtUserSelfService.read(); + fail("Failure expected on an invalid issuer"); + } catch (AccessControlException ex) { + // expected + } + } + + @Test + public void testExpiredToken() throws ParseException { + // Get an initial token + SyncopeClient adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD); + AccessTokenService accessTokenService = adminClient.getService(AccessTokenService.class); + + Response response = accessTokenService.login(); + String token = response.getHeaderString(RESTHeaders.TOKEN); + assertNotNull(token); + JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(token); + String tokenId = consumer.getJwtClaims().getTokenId(); + + // Create a new token using the Id of the first token + Date now = new Date(); + + Calendar expiry = Calendar.getInstance(); + expiry.setTime(now); + expiry.add(Calendar.MINUTE, 5); + + JwtClaims jwtClaims = new JwtClaims(); + jwtClaims.setTokenId(tokenId); + jwtClaims.setSubject("admin"); + jwtClaims.setIssuedAt(now.getTime()); + jwtClaims.setIssuer(JWT_ISSUER); + jwtClaims.setExpiryTime(now.getTime() - 5000L); + jwtClaims.setNotBefore(now.getTime()); + + JwsHeaders jwsHeaders = new JwsHeaders(JoseType.JWT, SignatureAlgorithm.HS512); + JwtToken jwtToken = new JwtToken(jwsHeaders, jwtClaims); + JwsJwtCompactProducer producer = new JwsJwtCompactProducer(jwtToken); + + JwsSignatureProvider jwsSignatureProvider = + new HmacJwsSignatureProvider(JWS_KEY.getBytes(), SignatureAlgorithm.HS512); + String signed = producer.signWith(jwsSignatureProvider); + + SyncopeClient jwtClient = clientFactory.create(signed); + UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class); + try { + jwtUserSelfService.read(); + fail("Failure expected on an expired token"); + } catch (AccessControlException ex) { + // expected + } + } + + @Test + public void testNotBefore() throws ParseException { + // Get an initial token + SyncopeClient adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD); + AccessTokenService accessTokenService = adminClient.getService(AccessTokenService.class); + + Response response = accessTokenService.login(); + String token = response.getHeaderString(RESTHeaders.TOKEN); + assertNotNull(token); + JwsJwtCompactConsumer consumer = new JwsJwtCompactConsumer(token); + String tokenId = consumer.getJwtClaims().getTokenId(); + + // Create a new token using the Id of the first token + Date now = new Date(); + + Calendar expiry = Calendar.getInstance(); + expiry.setTime(now); + expiry.add(Calendar.MINUTE, 5); + + JwtClaims jwtClaims = new JwtClaims(); + jwtClaims.setTokenId(tokenId); + jwtClaims.setSubject("admin"); + jwtClaims.setIssuedAt(now.getTime()); + jwtClaims.setIssuer(JWT_ISSUER); + jwtClaims.setExpiryTime(expiry.getTime().getTime()); + jwtClaims.setNotBefore(now.getTime() + 60000L); + + JwsHeaders jwsHeaders = new JwsHeaders(JoseType.JWT, SignatureAlgorithm.HS512); + JwtToken jwtToken = new JwtToken(jwsHeaders, jwtClaims); + JwsJwtCompactProducer producer = new JwsJwtCompactProducer(jwtToken); + + JwsSignatureProvider jwsSignatureProvider = + new HmacJwsSignatureProvider(JWS_KEY.getBytes(), SignatureAlgorithm.HS512); + String signed = producer.signWith(jwsSignatureProvider); + + SyncopeClient jwtClient = clientFactory.create(signed); + UserSelfService jwtUserSelfService = jwtClient.getService(UserSelfService.class); + try { + jwtUserSelfService.read(); + fail("Failure expected on a token that is not valid yet"); + } catch (AccessControlException ex) { + // expected + } + } +}