Skip to content

Commit

Permalink
feat: adding draft for integration test, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandra-bel authored and andreibogus committed Feb 22, 2024
1 parent 46c6321 commit ce75056
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
public class STSTokenValidationService {

private final TokenValidationUtils tokenValidationUtils;
public static final String ACCESS_TOKEN = "access_token";
private static final String ACCESS_TOKEN = "access_token";

/**
* Validates SI token and Access token.
Expand All @@ -61,8 +61,8 @@ public boolean validateToken(String token) {
if (accessToken.isPresent()) {
String accessTokenValue = accessToken.get();
JWTClaimsSet claimsAT = getClaimsSet(accessTokenValue);
tokenValidationUtils.checkIfAudienceClaimsEquals(claimsSI, claimsAT).ifPresent(errors::add);
tokenValidationUtils.checkIfNonceClaimsEquals(claimsSI, claimsAT).ifPresent(errors::add);
tokenValidationUtils.checkIfAudienceClaimsAreEqual(claimsSI, claimsAT).ifPresent(errors::add);
tokenValidationUtils.checkIfNonceClaimsAreEqual(claimsSI, claimsAT).ifPresent(errors::add);
} else {
errors.add("The '%s' claim must not be null.".formatted(ACCESS_TOKEN));
}
Expand Down Expand Up @@ -99,10 +99,7 @@ private JWTClaimsSet getClaimsSet(String token) {
private Optional<String> getAccessToken(JWTClaimsSet claims) {
try {
String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN);
if (accessTokenValue == null) {
return Optional.empty();
}
return Optional.of(accessTokenValue);
return accessTokenValue == null ? Optional.empty() : Optional.of(accessTokenValue);
} catch (ParseException e) {
throw new BadDataException("Could not parse jwt token", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import static java.time.ZoneOffset.UTC;

/**
* Methods for validating token claims.
* Contains methods for validating token claims.
*/
@Component
@RequiredArgsConstructor
Expand All @@ -53,27 +53,25 @@ public class TokenValidationUtils {
public Optional<String> checkIfIssuerEqualsSubject(JWTClaimsSet claims) {
String iss = claims.getIssuer();
String sub = claims.getSubject();
if (iss != null && Objects.equals(iss, sub)) {
return Optional.empty();
}
return Optional.of("The 'iss' and 'sub' claims must be non-null and identical.");
return (iss != null && Objects.equals(iss, sub)) ?
Optional.empty() : Optional.of("The 'iss' and 'sub' claims must be non-null and identical.");
}

public Optional<String> checkIfSubjectValidAndEqualsDid(JWTClaimsSet claims) {
String sub = claims.getSubject();
if (sub != null && sub.startsWith(DID_FORMAT)) {
URI id = service.getDidDocument(sub).getId();
if (id != null && Objects.equals(id.toString(), sub)) {
return Optional.empty();
}
return Optional.of("The 'sub' claim must be identical to the id of existing DID document.");
return (id != null && Objects.equals(id.toString(), sub)) ?
Optional.empty() : Optional.of("The 'sub' claim must be identical to the id of existing DID document.");
} else {
return Optional.of("The 'sub' claim must be in did format.");
}
return Optional.of("The 'sub' claim must be in did format.");
}

public Optional<String> checkTokenExpiry(JWTClaimsSet claims) {
Instant now = Instant.now();
Date expires = claims.getExpirationTime();

if (expires == null) {
return Optional.of("Required expiration time 'exp' claim is missing in token");
} else if (now.isAfter(convertDateToUtcTime(expires))) {
Expand All @@ -86,7 +84,7 @@ public Optional<String> checkTokenExpiry(JWTClaimsSet claims) {
if (issuedAtInst.isAfter(convertDateToUtcTime(expires))) {
return Optional.of("Issued at 'iat' claim is after expiration time 'exp' claim in token");
} else if (now.plusSeconds(IAT_LEEWAY).isBefore(issuedAtInst)) {
return Optional.of("Current date/time before issued at 'iat' claim in token");
return Optional.of("Current date/time is before issued at 'iat' claim in token");
}
}
return Optional.empty();
Expand All @@ -96,31 +94,32 @@ private Instant convertDateToUtcTime(Date date) {
return date.toInstant().atOffset(UTC).toInstant();
}

public Optional<String> checkIfAudienceClaimsEquals(JWTClaimsSet claimsSI, JWTClaimsSet claimsAT) {
public Optional<String> checkIfAudienceClaimsAreEqual(JWTClaimsSet claimsSI, JWTClaimsSet claimsAT) {
List<String> audienceSI = claimsSI.getAudience();
List<String> audienceAccess = claimsAT.getAudience();

if (audienceSI.isEmpty() || audienceAccess.isEmpty()) {
return Optional.of("The 'aud' claim must not be empty.");
} else if (audienceSI.contains(audienceAccess.get(0))) {
return (audienceAccess.get(0).startsWith(DID_FORMAT)) ?
Optional.empty() : Optional.of("The 'aud' claims must have did format.");
} else {
String audSI = audienceSI.get(0);
String audAT = audienceAccess.get(0);
if (audSI.equals(audAT)) {
return Optional.empty();
}
return Optional.of("The 'aud' claims must be equals in SI and Access tokens.");
return Optional.of("The 'aud' claims must be equal in SI and Access tokens.");
}
}

public Optional<String> checkIfNonceClaimsEquals(JWTClaimsSet claimsSI, JWTClaimsSet claimsAT) {
public Optional<String> checkIfNonceClaimsAreEqual(JWTClaimsSet claimsSI, JWTClaimsSet claimsAT) {
try {
String nonceSI = claimsSI.getStringClaim(NONCE);
String nonceAccess = claimsAT.getStringClaim(NONCE);

if (nonceSI == null || nonceAccess == null) {
return Optional.of("The 'nonce' claim must not be empty.");
} else if (nonceSI.equals(nonceAccess)) {
return Optional.empty();
} else {
return Optional.of("The 'nonce' claims must be equal in SI and Access tokens.");
}
return Optional.of("The 'nonce' claims must be equals in SI and Access tokens.");
} catch (ParseException e) {
throw new BadDataException("Could not parse 'nonce' claim in token", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.service;

import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import org.eclipse.tractusx.managedidentitywallets.ManagedIdentityWalletsApplication;
import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings;
import org.eclipse.tractusx.managedidentitywallets.config.TestContextInitializer;
import org.eclipse.tractusx.managedidentitywallets.dao.entity.Wallet;
import org.eclipse.tractusx.managedidentitywallets.dao.repository.WalletRepository;
import org.eclipse.tractusx.managedidentitywallets.utils.TokenValidationUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;

import static com.nimbusds.jose.jwk.Curve.Ed25519;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.BPN_1;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_1;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_BPN_2;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestConstants.DID_JSON_STRING_1;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.addAccessTokenToClaimsSet;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildClaimsSet;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildJWTToken;
import static org.eclipse.tractusx.managedidentitywallets.utils.TestUtils.buildWallet;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = { ManagedIdentityWalletsApplication.class })
@ContextConfiguration(initializers = { TestContextInitializer.class })
class STSTokenValidationServiceTest {

private static final OctetKeyPair JWK_OUTER = new OctetKeyPair
.Builder(Ed25519, new Base64URL("4Q5HCXPyutfcj7gLmbAKlYttlJPkykIkRjh7DH2NtZ0"))
.d(new Base64URL("Ktp0sv9dKr_gnzRxpH5V9qpiTgZ1WbkMSv8WtWodewg"))
.build();

private static final OctetKeyPair JWK_INNER = new OctetKeyPair
.Builder(Ed25519, new Base64URL("Z-8DEkN6pw2E01niDWqrp1kROLF-syIPIpFgmyrVUOU"))
.d(new Base64URL("MLYxSai_oFzuqEfnB2diA3oDuixLg3kQzZKMyW31-2o"))
.build();

@Autowired
private STSTokenValidationService stsTokenValidationService;

@Autowired
private TokenValidationUtils tokenValidationUtils;

@Autowired
private DidDocumentService didDocumentService;

@Autowired
private CommonService commonService;

@Autowired
private WalletRepository walletRepository;

@Autowired
private MIWSettings miwSettings;

@AfterEach
public void cleanWallets() {
walletRepository.deleteAll();
}

@Test
void validateTokenFailureAccessTokenMissingTest() throws JOSEException {
Wallet wallet = buildWallet(BPN_1, DID_BPN_1, DID_JSON_STRING_1);
walletRepository.save(wallet);

JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, "123456", Long.parseLong("2559397136000"));
String siToken = buildJWTToken(JWK_OUTER, outerSet);
boolean isValid = stsTokenValidationService.validateToken(siToken);

Assertions.assertFalse(isValid);
}

@Test
void validateTokenSuccessTest() throws JOSEException {
Wallet wallet = buildWallet(BPN_1, DID_BPN_1, DID_JSON_STRING_1);
walletRepository.save(wallet);

JWTClaimsSet innerSet = buildClaimsSet(DID_BPN_1, DID_BPN_2, DID_BPN_1, "123456", Long.parseLong("2559397136000"));
String accessToken = buildJWTToken(JWK_INNER, innerSet);

JWTClaimsSet outerSet = buildClaimsSet(DID_BPN_1, DID_BPN_1, DID_BPN_1, "123456", Long.parseLong("2559397136000"));
JWTClaimsSet outerSetFull = addAccessTokenToClaimsSet(accessToken, outerSet);
String siToken = buildJWTToken(JWK_OUTER, outerSetFull);

boolean isValid = stsTokenValidationService.validateToken(siToken);

Assertions.assertTrue(isValid);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
* ******************************************************************************
*/

package org.eclipse.tractusx.managedidentitywallets.utils;

public class TestConstants {

public static final String DID_BPN_1 = "did:web:localhost:BPNL000000000001";
public static final String DID_BPN_2 = "did:web:localhost:BPNL000000000002";
public static final String BPN_1 = "BPNL000000000001";
public static final String BPN_2 = "BPNL000000000002";
public static final String DID_JSON_STRING_1 = """
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3c.github.io/vc-jws-2020/contexts/v1"
],
"id": "did:web:localhost:BPNL000000000001",
"verificationMethod": [
{
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "4Q5HCXPyutfcj7gLmbAKlYttlJPkykIkRjh7DH2NtZ0"
},
"controller": "did:web:localhost:BPNL000000000001",
"id": "did:web:localhost:BPNL000000000001#58cb4b32-c2e4-46f0-a3ad-3286e34765ed",
"type": "JsonWebKey2020"
}
]
}
""";
public static final String DID_JSON_STRING_2 = """
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3c.github.io/vc-jws-2020/contexts/v1"
],
"id": "did:web:localhost:BPNL000000000002",
"verificationMethod": [
{
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "Z-8DEkN6pw2E01niDWqrp1kROLF-syIPIpFgmyrVUOU"
},
"controller": "did:web:localhost:BPNL000000000002",
"id": "did:web:localhost:BPNL000000000001#58cb4b32-c2e4-46f0-a3ad-3286e34765ed",
"type": "JsonWebKey2020"
}
]
}
""";
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.curiousoddman.rgxgen.RgxGen;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.Ed25519Signer;
import com.nimbusds.jose.jwk.OctetKeyPair;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.eclipse.tractusx.managedidentitywallets.config.MIWSettings;
import org.eclipse.tractusx.managedidentitywallets.constant.MIWVerifiableCredentialType;
import org.eclipse.tractusx.managedidentitywallets.constant.RestURI;
Expand Down Expand Up @@ -52,6 +60,7 @@

import java.net.URI;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -205,4 +214,39 @@ public static String getRandomBpmNumber() {
RgxGen rgxGen = new RgxGen(StringPool.BPN_NUMBER_REGEX);
return rgxGen.generate();
}

public static String buildJWTToken(OctetKeyPair jwk, JWTClaimsSet claimsSet) throws JOSEException {
JWSSigner signer = new Ed25519Signer(jwk);
SignedJWT signedJWT = new SignedJWT(
new JWSHeader.Builder(JWSAlgorithm.EdDSA).keyID(jwk.getKeyID()).build(),
claimsSet);

signedJWT.sign(signer);

return signedJWT.serialize();
}

public static JWTClaimsSet buildClaimsSet(String issuer, String subject, String audience, String nonce, long expiration) {
return new JWTClaimsSet.Builder()
.subject(subject)
.issuer(issuer)
.audience(audience)
.expirationTime(new Date(expiration))
.claim("nonce", nonce)
.build();
}

public static JWTClaimsSet addAccessTokenToClaimsSet(String accessToken, JWTClaimsSet initialSet) {
return new JWTClaimsSet.Builder(initialSet).claim("access_token", accessToken).build();
}

public static Wallet buildWallet(String bpn, String did, String didJson) {
return Wallet.builder()
.bpn(bpn)
.did(did)
.didDocument(DidDocument.fromJson(didJson))
.algorithm(StringPool.ED_25519)
.name(bpn)
.build();
}
}
Loading

0 comments on commit ce75056

Please sign in to comment.