All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.cidaas.quarkus.extension.token.validation.OfflineTokenValidationService Maven / Gradle / Ivy

package de.cidaas.quarkus.extension.token.validation;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.microprofile.config.ConfigProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.cidaas.quarkus.extension.runtime.CacheService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
import jakarta.json.JsonString;

@ApplicationScoped
public class OfflineTokenValidationService implements ValidationService {
	@Inject
	CacheService cacheService;

	private static final Logger LOG = LoggerFactory.getLogger(OfflineTokenValidationService.class);

	/**
	 * validate token without calling introspection endpoint.
	 *
	 * @param tokenValidationRequest contain access token and definition which claims
	 *                               to be validated and how.
	 * 
	 * @return true if tokenValidationRequest is valid, false if invalid
	 */
	@Override
	public boolean validateToken(TokenValidationRequest tokenValidationRequest) {
		JsonObject header = JwtUtil.decodeHeader(tokenValidationRequest.getToken());

		if (header == null || validateTokenHeader(header) == false) {
			return false;
		}

		JsonObject payload = JwtUtil.decodePayload(tokenValidationRequest.getToken());

		if (payload == null || validateGeneralInfo(payload) == false) {
			return false;
		}

		List toBeValidated = new ArrayList<>();

		if (tokenValidationRequest.getScopes() != null && !tokenValidationRequest.getScopes().isEmpty()) {
			toBeValidated.add(validateScopes(tokenValidationRequest, payload));
		}

		if (tokenValidationRequest.getRoles() != null && !tokenValidationRequest.getRoles().isEmpty()) {
			toBeValidated.add(validateRoles(tokenValidationRequest, payload));
		}

		if (tokenValidationRequest.getGroups() != null && !tokenValidationRequest.getGroups().isEmpty()) {
			toBeValidated.add(validateGroups(tokenValidationRequest, payload));
		}

		if (toBeValidated.isEmpty() == true) {
			return true;
		}

		if (tokenValidationRequest.isStrictValidation() == true) {
			return !toBeValidated.contains(false);
		}

		return toBeValidated.contains(true);

	}

	/**
	 * validate header part of access token.
	 *
	 * @param header to be validated.
	 * 
	 * @return true if header‚ is valid, false if invalid
	 */
	boolean validateTokenHeader(JsonObject header) {
		JsonObject jwks = cacheService.getJwks();

		if (jwks == null) {
			LOG.error("jwk is null!");
			throw new TokenValidationException("JWK invalid!");
		}

		JsonArray keys = jwks.getJsonArray("keys");

		if (keys == null || keys.isEmpty()) {
			LOG.error("keys couldn't be found!");
			throw new TokenValidationException("JWK invalid!");
		}

		String kid = header.getString("kid", null);
		String alg = header.getString("alg", null);

		if (kid == null || alg == null) {
			LOG.error("header is invalid!");
			throw new TokenValidationException("Header invalid!");
		}

		for (int i = 0; i < keys.size(); i++) {
			JsonObject key = keys.getJsonObject(i);
			String keyKid = key.getString("kid");
			String keyAlg = key.getString("alg");
			if (kid.equals(keyKid) && alg.equals(keyAlg)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * validate payload part of access token.
	 *
	 * @param payload to be validated.
	 * 
	 * @return true if payload is valid, false if invalid
	 */
	boolean validateGeneralInfo(JsonObject payload) {
		String baseUrl = ConfigProvider.getConfig()
				.getValue("de.cidaas.quarkus.extension.runtime.CidaasClient/mp-rest/url", String.class);

		if (payload.getString("iss", null) == null) {
			LOG.warn("token doesn't have iss!");
			return false;
		}

		if (!payload.getString("iss").equals(baseUrl)) {
			LOG.warn("iss is invalid!");
			return false;
		}

		int dateAsNumber = payload.getInt("exp");
		Instant expirationDate = Instant.ofEpochSecond(dateAsNumber);

		if (expirationDate.compareTo(Instant.now()) < 0) {
			LOG.warn("token is expired!");
			return false;
		}

		return true;
	}

	private boolean validateScopes(TokenValidationRequest tokenValidationRequest, JsonObject payload) {
		JsonArray scopes = payload.getJsonArray("scopes");
		if (scopes == null) {
			return false;
		}
		List scopesFromToken = scopes.getValuesAs(JsonString::getString);
		if (tokenValidationRequest.isStrictScopeValidation() == true
				&& !scopesFromToken.containsAll(tokenValidationRequest.getScopes())) {
			LOG.warn("token doesn't have enough scopes!");
			return false;
		}
		if (tokenValidationRequest.isStrictScopeValidation() == false && !scopesFromToken.stream()
				.anyMatch(element -> tokenValidationRequest.getScopes().contains(element))) {
			LOG.warn("token doesn't have enough scopes!");
			return false;
		}
		return true;
	}

	private boolean validateRoles(TokenValidationRequest tokenValidationRequest, JsonObject payload) {
		JsonArray roles = payload.getJsonArray("roles");
		if (roles == null) {
			return false;
		}
		List rolesFromToken = roles.getValuesAs(JsonString::getString);
		if (tokenValidationRequest.isStrictRoleValidation() == true
				&& !rolesFromToken.containsAll(tokenValidationRequest.getRoles())) {
			LOG.warn("token doesn't have enough roles!");
			return false;
		}
		if (tokenValidationRequest.isStrictRoleValidation() == false
				&& !rolesFromToken.stream().anyMatch(element -> tokenValidationRequest.getRoles().contains(element))) {
			LOG.warn("token doesn't have enough roles!");
			return false;
		}
		return true;
	}

	private boolean validateGroups(TokenValidationRequest tokenValidationRequest, JsonObject payload) {
		JsonArray groups = payload.getJsonArray("groups");
		if (groups == null) {
			return false;
		}
		boolean strictGroupValidation = tokenValidationRequest.isStrictGroupValidation();
		boolean isAllGroupValid = true;

		List groupsFromToken = new ArrayList<>();
		for (int i = 0; i < groups.size(); i++) {
			JsonObject groupFromToken = groups.getJsonObject(i);
			String groupIdFromToken = groupFromToken.getString("groupId");
			List groupRolesFromToken = groupFromToken.getJsonArray("roles").getValuesAs(JsonString::getString);
			Group group = new Group(groupIdFromToken, groupRolesFromToken);
			groupsFromToken.add(group);
		}

		for (Group groupFromValidationRequest : tokenValidationRequest.getGroups()) {
			boolean isGroupValid = validateGroup(groupFromValidationRequest, groupsFromToken);

			if (isGroupValid == true && strictGroupValidation == false) {
				return true;
			}
			if (isGroupValid == false) {
				isAllGroupValid = false;
				if (strictGroupValidation == true) {
					LOG.warn("token doesn't have enough groups!");
					return false;
				}
			}
		}

		return isAllGroupValid;

	}

	private boolean validateGroup(Group groupFromValidationRequest, List groupsFromToken) {
		String groupIdFromValidationRequest = groupFromValidationRequest.getGroupId();
		List groupRolesFromValidationRequest = groupFromValidationRequest.getRoles();
		boolean strictGroupRoleValidation = groupFromValidationRequest.isStrictRoleValidation();

		for (Group groupFromToken : groupsFromToken) {
			if (groupFromToken.getGroupId().equals(groupIdFromValidationRequest)) {
				if (strictGroupRoleValidation == true
						&& groupFromToken.getRoles().containsAll(groupRolesFromValidationRequest)) {
					return true;
				}
				if (strictGroupRoleValidation == false && groupFromToken.getRoles().stream()
						.anyMatch(element -> groupRolesFromValidationRequest.contains(element))) {
					return true;
				}
			}
		}
		LOG.warn("grouproles is invalid!");
		return false;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy