Skip to content
Permalink
Browse files
Merge pull request #3 from myrle-krantz/develop
Moving TenantRefreshTokenSerializer from identity into anubis.
  • Loading branch information
myrle-krantz committed Apr 26, 2017
2 parents 57a6751 + 2f58169 commit a68574b61fa14148f319fa793e8189d641a10b56
Showing 10 changed files with 409 additions and 26 deletions.
@@ -22,6 +22,9 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
* @author Myrle Krantz
*/
@FeignClient(name="anubis-v1", path="/anubis/v1", configuration = CustomFeignClientsConfiguration.class)
public interface Example {
@RequestMapping(value = "/dummy", method = RequestMethod.GET,
@@ -20,6 +20,9 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
* @author Myrle Krantz
*/
@FeignClient(name="anubis-v1", path="/anubis/v1", configuration = CustomFeignClientsConfiguration.class)
public interface Example {
@RequestMapping(value = "initialize", method = RequestMethod.POST)
@@ -20,6 +20,9 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
* @author Myrle Krantz
*/
@FeignClient(name="anubis-v1", path="/anubis/v1", configuration = CustomFeignClientsConfiguration.class)
public interface Example {
@RequestMapping(value = "initialize", method = RequestMethod.GET)
@@ -29,6 +29,7 @@
import io.mifos.anubis.service.PermittableService;
import io.mifos.anubis.token.SystemAccessTokenSerializer;
import io.mifos.anubis.token.TenantAccessTokenSerializer;
import io.mifos.anubis.token.TenantRefreshTokenSerializer;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

@@ -46,8 +47,9 @@ class AnubisImportSelector implements ImportSelector {
classesToImport.add(TenantRsaKeyProvider.class);
classesToImport.add(SystemRsaKeyProvider.class);

classesToImport.add(TenantAccessTokenSerializer.class);
classesToImport.add(SystemAccessTokenSerializer.class);
classesToImport.add(TenantAccessTokenSerializer.class);
classesToImport.add(TenantRefreshTokenSerializer.class);

classesToImport.add(IsisAuthenticatedAuthenticationProvider.class);
classesToImport.add(TenantAuthenticator.class);
@@ -19,7 +19,7 @@
* @author Myrle Krantz
*/
public class InvalidKeyTimestampException extends Exception {
InvalidKeyTimestampException(final String version) {
public InvalidKeyTimestampException(final String version) {
super("Invalid key version: " + version);
}
}
@@ -0,0 +1,168 @@
/*
* Copyright 2017 The Mifos Initiative.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 io.mifos.anubis.token;

import io.jsonwebtoken.*;
import io.mifos.anubis.api.v1.TokenConstants;
import io.mifos.anubis.provider.InvalidKeyTimestampException;
import io.mifos.anubis.provider.TenantRsaKeyProvider;
import io.mifos.anubis.security.AmitAuthenticationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Nonnull;
import java.security.Key;
import java.security.PrivateKey;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
* @author Myrle Krantz
*/
@SuppressWarnings("WeakerAccess")
@Component
public class TenantRefreshTokenSerializer {

final private TenantRsaKeyProvider tenantRsaKeyProvider;

@SuppressWarnings("SpringJavaAutowiringInspection")
@Autowired
TenantRefreshTokenSerializer(final TenantRsaKeyProvider tenantRsaKeyProvider) {
this.tenantRsaKeyProvider = tenantRsaKeyProvider;
}

@SuppressWarnings("WeakerAccess")
public static class Specification {
private String keyTimestamp;
private PrivateKey privateKey;
private String user;
private long secondsToLive;
private String sourceApplication;

public Specification setKeyTimestamp(final String keyTimestamp) {
this.keyTimestamp = keyTimestamp;
return this;
}

public Specification setPrivateKey(final PrivateKey privateKey) {
this.privateKey = privateKey;
return this;
}

public Specification setUser(final String user) {
this.user = user;
return this;
}

public Specification setSecondsToLive(final long secondsToLive) {
this.secondsToLive = secondsToLive;
return this;
}

public Specification setSourceApplication(final String sourceApplication) {
this.sourceApplication = sourceApplication;
return this;
}
}

public TokenSerializationResult build(final Specification specification)
{
final long issued = System.currentTimeMillis();

if (specification.keyTimestamp == null) {
throw new IllegalArgumentException("token signature timestamp must not be null.");
}
if (specification.privateKey == null) {
throw new IllegalArgumentException("token signature privateKey must not be null.");
}
if (specification.sourceApplication == null) {
throw new IllegalArgumentException("token source application must not be null.");
}

final JwtBuilder jwtBuilder =
Jwts.builder()
.setIssuer(specification.sourceApplication)
.setSubject(specification.user)
.claim(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, specification.keyTimestamp)
.setIssuedAt(new Date(issued))
.signWith(SignatureAlgorithm.RS512, specification.privateKey);
if (specification.secondsToLive <= 0) {
throw new IllegalArgumentException("token secondsToLive must be positive.");
}

final Date expiration = new Date(issued + TimeUnit.SECONDS.toMillis(specification.secondsToLive));
jwtBuilder.setExpiration(expiration);

return new TokenSerializationResult(TokenConstants.PREFIX + jwtBuilder.compact(), expiration);
}

public TokenDeserializationResult deserialize(final String refreshToken)
{
final Optional<String> tokenString = getJwtTokenString(refreshToken);

final String token = tokenString.orElseThrow(AmitAuthenticationException::invalidToken);


try {
final JwtParser parser = Jwts.parser().setSigningKeyResolver(new SigningKeyResolver() {
@Override public Key resolveSigningKey(final JwsHeader header, final Claims claims) {
final String keyTimestamp = getKeyTimestampFromClaims(claims);

try {
return tenantRsaKeyProvider.getPublicKey(keyTimestamp);
}
catch (final IllegalArgumentException e)
{
throw AmitAuthenticationException.missingTenant();
}
catch (final InvalidKeyTimestampException e)
{
throw AmitAuthenticationException.invalidTokenKeyTimestamp(TokenType.TENANT.getIssuer(), keyTimestamp);
}
}

@Override public Key resolveSigningKey(final JwsHeader header, final String plaintext) {
return null;
}
});

@SuppressWarnings("unchecked") Jwt<Header, Claims> jwt = parser.parse(token);

return new TokenDeserializationResult(jwt.getBody().getSubject(), jwt.getBody().getExpiration(), jwt.getBody().getIssuer());
}
catch (final JwtException e) {
throw AmitAuthenticationException.invalidToken();
}
}

private static Optional<String> getJwtTokenString(final String refreshToken) {
if ((refreshToken == null) || refreshToken.equals(
TokenConstants.NO_AUTHENTICATION)){
return Optional.empty();
}

if (!refreshToken.startsWith(TokenConstants.PREFIX)) {
throw AmitAuthenticationException.invalidToken();
}
return Optional.of(refreshToken.substring(TokenConstants.PREFIX.length()).trim());
}

private @Nonnull
String getKeyTimestampFromClaims(final Claims claims) {
return claims.get(TokenConstants.JWT_SIGNATURE_TIMESTAMP_CLAIM, String.class);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2017 The Mifos Initiative.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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 io.mifos.anubis.token;

import java.util.Date;

/**
* @author Myrle Krantz
*/
@SuppressWarnings("WeakerAccess")
public class TokenDeserializationResult {
final private String userIdentifier;
final private Date expiration;
final private String sourceApplication;

TokenDeserializationResult(final String userIdentifier, final Date expiration, final String sourceApplication) {
this.userIdentifier = userIdentifier;
this.expiration = expiration;
this.sourceApplication = sourceApplication;
}

public String getUserIdentifier() {
return userIdentifier;
}

public Date getExpiration() {
return expiration;
}

public String getSourceApplication() {
return sourceApplication;
}
}
@@ -15,20 +15,19 @@
*/
package io.mifos.anubis.token;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.mifos.anubis.api.v1.TokenConstants;
import io.mifos.core.lang.security.RsaKeyPairFactory;
import io.mifos.core.test.domain.TimeStampChecker;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.mifos.core.lang.security.RsaKeyPairFactory;

/**
* @author Myrle Krantz, Markus Geiss
@@ -55,7 +54,7 @@ public static void initialize()

@SuppressWarnings({"unchecked"})
@Test
public void shouldCreateValidSeshatToken() throws Exception {
public void shouldCreateValidSystemToken() throws Exception {

final SystemAccessTokenSerializer.Specification specification
= new SystemAccessTokenSerializer.Specification()
@@ -68,17 +67,13 @@ public void shouldCreateValidSeshatToken() throws Exception {

final SystemAccessTokenSerializer testSubject = new SystemAccessTokenSerializer();

final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
final TimeStampChecker timeStampChecker = TimeStampChecker.inTheFuture(Duration.ofSeconds(SECONDS_TO_LIVE));
final TokenSerializationResult systemToken = testSubject.build(specification);

Assert.assertNotNull(systemToken);

final LocalDateTime expiration = systemToken.getExpiration();
final long diff = expiration.toInstant(ZoneOffset.ofHours(0)).getEpochSecond()
- now.toInstant(ZoneOffset.ofHours(0)).getEpochSecond();

Assert.assertTrue("The expiration should be 15(+/- 1) seconds from now. Instead it is: " + diff,
Math.abs(diff - SECONDS_TO_LIVE) <= 1);
timeStampChecker.assertCorrect(expiration);

final Jwt<Header, Claims> parsedToken = Jwts
.parser()
@@ -23,13 +23,13 @@
import io.mifos.anubis.api.v1.TokenConstants;
import io.mifos.anubis.api.v1.domain.TokenContent;
import io.mifos.core.lang.security.RsaKeyPairFactory;
import io.mifos.core.test.domain.TimeStampChecker;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Collections;

/**
@@ -51,7 +51,7 @@ public static void initialize()

@SuppressWarnings({"unchecked"})
@Test
public void shouldCreateValidSeshatToken() throws Exception
public void shouldCreateValidAccessToken() throws Exception
{
final TenantAccessTokenSerializer.Specification specification
= new TenantAccessTokenSerializer.Specification()
@@ -63,17 +63,13 @@ public void shouldCreateValidSeshatToken() throws Exception

final TenantAccessTokenSerializer testSubject = new TenantAccessTokenSerializer(new Gson());

final LocalDateTime now = LocalDateTime.now(ZoneId.of("UTC"));
final TimeStampChecker timeStampChecker = TimeStampChecker.inTheFuture(Duration.ofSeconds(SECONDS_TO_LIVE));
final TokenSerializationResult systemToken = testSubject.build(specification);

Assert.assertNotNull(systemToken);

final LocalDateTime expiration = systemToken.getExpiration();
final long diff = expiration.toInstant(ZoneOffset.ofHours(0)).getEpochSecond()
- now.toInstant(ZoneOffset.ofHours(0)).getEpochSecond();

Assert.assertTrue("The expiration should be 15(+/- 1) seconds from now. Instead it is: " + diff,
Math.abs(diff - SECONDS_TO_LIVE) <= 1);
timeStampChecker.assertCorrect(expiration);

final Jwt<Header, Claims> parsedToken = Jwts
.parser()

0 comments on commit a68574b

Please sign in to comment.