Skip to content

Commit

Permalink
feat: create initial classes with validation
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 061faa7 commit fc5db81
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,90 @@

package org.eclipse.tractusx.managedidentitywallets.service;

import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException;
import org.eclipse.tractusx.managedidentitywallets.utils.TokenValidationUtils;
import org.springframework.stereotype.Service;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
@Slf4j
@RequiredArgsConstructor
public class STSTokenValidationService {

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

/**
* Validates SI token and Access token.
*
* @param token token in a String format
* @return boolean result of validation
*/
public boolean validateToken(String token) {
return true;
List<String> errors = new ArrayList<>();

JWTClaimsSet claimsSI = getClaimsSet(token);

tokenValidationUtils.checkIfIssuerEqualsSubject(claimsSI).ifPresent(errors::add);
tokenValidationUtils.checkTokenExpiry(claimsSI).ifPresent(errors::add);
tokenValidationUtils.checkIfSubjectValidAndEqualsDid(claimsSI).ifPresent(errors::add);

Optional<String> accessToken = getAccessToken(claimsSI);
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);
} else {
errors.add("The '%s' claim must not be null.".formatted(ACCESS_TOKEN));
}

if (errors.isEmpty()) {
return true;
} else {
log.error(errors.toString());
return false;
}
}

/**
* Parses the token and gets claim set from it.
*
* @param token token in a String format
* @return the set of JWT claims
*/
private JWTClaimsSet getClaimsSet(String token) {
try {
SignedJWT tokenParsed = SignedJWT.parse(token);
return tokenParsed.getJWTClaimsSet();
} catch (ParseException e) {
throw new BadDataException("Could not parse jwt token", e);
}
}

/**
* Gets access token from SI token.
*
* @param claims set of claims of SI token
* @return the value of token
*/
private Optional<String> getAccessToken(JWTClaimsSet claims) {
try {
String accessTokenValue = claims.getStringClaim(ACCESS_TOKEN);
if (accessTokenValue == null) {
return Optional.empty();
}
return Optional.of(accessTokenValue);
} catch (ParseException e) {
throw new BadDataException("Could not parse jwt token", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* *******************************************************************************
* Copyright (c) 2021,2023 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;

import com.nimbusds.jwt.JWTClaimsSet;
import lombok.RequiredArgsConstructor;
import org.eclipse.tractusx.managedidentitywallets.exception.BadDataException;
import org.eclipse.tractusx.managedidentitywallets.service.DidDocumentService;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.text.ParseException;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static java.time.ZoneOffset.UTC;

/**
* Methods for validating token claims.
*/
@Component
@RequiredArgsConstructor
public class TokenValidationUtils {

private final DidDocumentService service;

public static final String NONCE = "nonce";
public static final String DID_FORMAT = "did:";
private static final int MAX_TOKEN_AGE = 60;

public Optional<String> checkIfIssuerEqualsSubject(JWTClaimsSet claims) {
String iss = claims.getIssuer();
String sub = claims.getSubject();
if (!(iss != null && Objects.equals(iss, sub))) {
return Optional.of("The 'iss' and 'sub' claims must be non-null and identical.");
}
return Optional.empty();
}

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.of("The 'sub' claim must be identical to the id of existing DID document.");
}
return Optional.empty();
}
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))) {
return Optional.of("Token has expired (exp)");
}

Date issuedAt = claims.getIssueTime();
if (issuedAt != null) {
Instant issuedAtInst = convertDateToUtcTime(issuedAt);
if (issuedAtInst.isAfter(convertDateToUtcTime(expires))) {
return Optional.of("Issued at (iat) claim is after expiration time (exp) claim in token");
} else if (now.plusSeconds(MAX_TOKEN_AGE).isBefore(issuedAtInst)) {
return Optional.of("Current date/time before issued at (iat) claim in token");
}
}
return Optional.empty();
}

private Instant convertDateToUtcTime(Date date) {
return date.toInstant().atOffset(UTC).toInstant();
}

public Optional<String> checkIfAudienceClaimsEquals(JWTClaimsSet claimsSI, JWTClaimsSet claimsAT) {
List<String> audienceSI = claimsSI.getAudience();
List<String> audienceAccess = claimsAT.getAudience();
if (!(audienceSI.isEmpty() && audienceAccess.isEmpty())) {
String audSI = audienceSI.get(0);
String audAT = audienceAccess.get(0);
if (!(audSI.equals(audAT))) {
return Optional.of("The 'aud' claims must be equals in SI and Access tokens.");
}
return Optional.empty();
} else {
return Optional.of("The 'aud' claim must not be empty.");
}
}

public Optional<String> checkIfNonceClaimsEquals(JWTClaimsSet claimsSI, JWTClaimsSet claimsAT) {
try {
String nonceSI = claimsSI.getStringClaim(NONCE);
String nonceAccess = claimsAT.getStringClaim(NONCE);
if (!(nonceSI == null) && !(nonceAccess == null)) {
if (!(nonceSI.equals(nonceAccess))) {
return Optional.of("The 'nonce' claims must be equals in SI and Access tokens.");
}
return Optional.empty();
} else {
return Optional.of("The 'nonce' claim must not be empty.");
}
} catch (ParseException e) {
throw new BadDataException("Could not parse 'nonce' claim in token", e);
}
}
}

0 comments on commit fc5db81

Please sign in to comment.