BasicTokenParser.java

/*
 * GovWay - A customizable API Gateway 
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it).
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package org.openspcoop2.pdd.core.token.parser;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.openspcoop2.pdd.core.token.Costanti;
import org.openspcoop2.pdd.core.token.TokenUtilities;
import org.openspcoop2.utils.UtilsException;

/**     
 * BasicTokenParser
 *
 * @author Poli Andrea (poli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class BasicTokenParser implements ITokenParser {

	protected Integer httpResponseCode;
	protected String raw;
	protected Map<String, Serializable> claims;
	protected TipologiaClaims parser;
	protected Properties parserConfig;
	protected ITokenUserInfoParser userInfoParser;
	
	public BasicTokenParser(TipologiaClaims parser) {
		this(parser, null);
	}
	public BasicTokenParser(TipologiaClaims parser, Properties parserConfig) {
		this.parser = parser;
		this.parserConfig = parserConfig;
		this.userInfoParser = new BasicTokenUserInfoParser(parser, this.parserConfig);
	}
	
	@Override
	public void init(String raw, Map<String, Serializable> claims) {
		this.raw = raw;
		this.claims = claims;
		this.userInfoParser.init(raw, claims);
	}
	
	@Override
	public void checkHttpTransaction(Integer httpResponseCode) throws UtilsException{
		this.httpResponseCode = httpResponseCode;
		switch (this.parser) {
		case INTROSPECTION_RESPONSE_RFC_7662:
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
		case OIDC_ID_TOKEN:
		case MAPPING:
		case CUSTOM:
			if(this.httpResponseCode!=null && 
				(this.httpResponseCode.intValue() < 200 || this.httpResponseCode.intValue()>299)) {
				String msgError = "Connessione terminata con errore (codice trasporto: "+this.httpResponseCode.intValue()+")";
				throw new UtilsException(msgError+": "+this.raw);
			}
			break;
		case GOOGLE:
			if(this.httpResponseCode!=null &&
				(this.httpResponseCode.intValue() == 400 || this.httpResponseCode.intValue()==401) &&
				(this.claims!=null && this.claims.size()>0) 
				){
				String tmp = TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_ERROR);
				if(tmp==null) {
					tmp = TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_ERROR_DESCRIPTION);
				}
				if(tmp!=null) {
					return;
				}	
			}
			if(this.httpResponseCode!=null && 
				(this.httpResponseCode.intValue() < 200 || this.httpResponseCode.intValue()>299)) {
				String msgError = "Connessione terminata con errore (codice trasporto: "+this.httpResponseCode.intValue()+")";
				throw new UtilsException(msgError+": "+this.raw);
			}
			break;
		}
	}
	
	@Override
	public boolean isValid() {
		
		if(this.claims==null || this.claims.size()<=0) {
			return false;
		}
		
		switch (this.parser) {
		case INTROSPECTION_RESPONSE_RFC_7662:
			String claim = TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_ACTIVE);
			return Boolean.valueOf(claim);
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
		case OIDC_ID_TOKEN:
		case MAPPING:
		case CUSTOM:
			return true;
		case GOOGLE:
			if(this.httpResponseCode==null) {
				return true;
			}
			else {
				if(this.httpResponseCode.intValue() == 400 || this.httpResponseCode.intValue()==401) {
					String tmp = TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_ERROR);
					if(tmp==null) {
						tmp = TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_ERROR_DESCRIPTION);
					}
					if(tmp!=null) {
						return false;
					}
				}
			}
		}
		return true;
	}
	
	@Override
	public String getIssuer() {
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			return TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_ISSUER);
		case INTROSPECTION_RESPONSE_RFC_7662:
			return TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_ISSUER);
		case OIDC_ID_TOKEN:
			return TokenUtilities.getClaimAsString(this.claims,Claims.OIDC_ID_TOKEN_ISSUER);
		case GOOGLE:
			return TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_ISSUER);
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_ISSUER);
			return TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
		case CUSTOM:
			return null;
		}
		return null;
	}

	@Override
	public String getSubject() {
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			return TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_SUBJECT);
		case INTROSPECTION_RESPONSE_RFC_7662:
			return TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_SUBJECT);
		case OIDC_ID_TOKEN:
			return TokenUtilities.getClaimAsString(this.claims,Claims.OIDC_ID_TOKEN_SUBJECT);
		case GOOGLE:
			return TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_SUBJECT);
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_SUBJECT);
			return TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
		case CUSTOM:
			return null;
		}
		return null;
	}

	@Override
	public String getUsername() {
		
		// NOTA: e' importante per le ricerche, quindi va bene anche nome e cognome se non c'รจ username
		
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			return null; // unsupported
		case INTROSPECTION_RESPONSE_RFC_7662:
			return TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_USERNAME);
		case OIDC_ID_TOKEN:
			String tmp = TokenUtilities.getClaimAsString(this.claims,Claims.OIDC_ID_CLAIMS_PREFERRED_USERNAME);
			if(tmp==null &&
				this.getUserInfoParser()!=null) {
				return this.getUserInfoParser().getFullName();
			}
			return tmp;
		case GOOGLE:
			if(this.getUserInfoParser()!=null) {
				return this.getUserInfoParser().getFullName();
			}
			return null;
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_USERNAME);
			return TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
		case CUSTOM:
			return null;
		}
		return null;
	}

	@Override
	public List<String> getAudience() {
		List<String> lNull = null;
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			return TokenUtilities.getClaimAsList(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_AUDIENCE);
		case INTROSPECTION_RESPONSE_RFC_7662:
			return TokenUtilities.getClaimAsList(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_AUDIENCE);
		case OIDC_ID_TOKEN:
			return TokenUtilities.getClaimAsList(this.claims,Claims.OIDC_ID_TOKEN_AUDIENCE);
		case GOOGLE:
			return TokenUtilities.getClaimAsList(this.claims,Claims.GOOGLE_CLAIMS_AUDIENCE);
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_AUDIENCE);
			return TokenUtilities.getFirstClaimAsList(this.claims, claimNames);
		case CUSTOM:
			return lNull;
		}
		return lNull;
	}

	@Override
	public Date getExpired() {
		String tmp = null;
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_EXPIRED);
			break;
		case INTROSPECTION_RESPONSE_RFC_7662:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_EXPIRED);
			break;
		case OIDC_ID_TOKEN:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.OIDC_ID_TOKEN_EXPIRED);
			break;
		case GOOGLE:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_EXPIRED);
			break;
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_EXPIRE);
			tmp = TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
			break;
		case CUSTOM:
			return null;
		}
		if(tmp!=null) {
			return TokenUtils.parseTimeInSecond(tmp);
		}
		return null;
	}

	@Override
	public Date getIssuedAt() {
		String tmp = null;
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_ISSUED_AT);
			break;
		case INTROSPECTION_RESPONSE_RFC_7662:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_ISSUED_AT);
			break;
		case OIDC_ID_TOKEN:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.OIDC_ID_TOKEN_ISSUED_AT);
			break;
		case GOOGLE:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_ISSUED_AT);
			break;
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_ISSUED_AT);
			tmp = TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
			break;
		case CUSTOM:
			return null;
		}
		if(tmp!=null) {
			return TokenUtils.parseTimeInSecond(tmp);
		}
		return null;
	}

	@Override
	public Date getNotToBeUsedBefore() {
		String tmp = null;
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_NOT_TO_BE_USED_BEFORE);
			break;
		case INTROSPECTION_RESPONSE_RFC_7662:
			tmp =  TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_NOT_TO_BE_USED_BEFORE);
			break;
		case OIDC_ID_TOKEN:
		case GOOGLE:
			return null; // unsupported
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_NOT_TO_BE_USED_BEFORE);
			tmp = TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
			break;
		case CUSTOM:
			return null;
		}
		if(tmp!=null) {
			return TokenUtils.parseTimeInSecond(tmp);
		}
		return null;
	}

	@Override
	public String getJWTIdentifier() {
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			return TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_RFC_7519_JWT_ID);
		case INTROSPECTION_RESPONSE_RFC_7662:
			return TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_JWT_ID);
		case OIDC_ID_TOKEN:
		case GOOGLE:
			return null; // unsupported
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_JWT_IDENTIFIER);
			return TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
		case CUSTOM:
			return null;
		}
		return null;
	}
	
	@Override
	public String getClientId() {
		switch (this.parser) {
		case JSON_WEB_TOKEN_RFC_7519:
			return null; // unsupported
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
			return TokenUtilities.getClaimAsString(this.claims,Claims.JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068_CLIENT_ID);
		case INTROSPECTION_RESPONSE_RFC_7662:
			return TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_CLIENT_ID);
		case OIDC_ID_TOKEN:
			return TokenUtilities.getClaimAsString(this.claims,Claims.OIDC_ID_TOKEN_AZP);
		case GOOGLE:
			return TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_AZP);
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_CLIENT_ID);
			return TokenUtilities.getFirstClaimAsString(this.claims, claimNames);
		case CUSTOM:
			return null;
		}
		return null;
	}

	@Override
	public List<String> getRoles() {
		List<String> lNull = null;
		switch (this.parser) {
		case MAPPING:
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_ROLE);
			return TokenUtilities.getFirstClaimAsList(this.claims, claimNames);
		case JSON_WEB_TOKEN_RFC_7519:
		case JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068:
		case INTROSPECTION_RESPONSE_RFC_7662:
		case OIDC_ID_TOKEN:
		case GOOGLE:
		case CUSTOM:
			return lNull;
		}
		return lNull;
	}

	@Override
	public List<String> getScopes() {
		
		String tmpScopes = null;
		if(TipologiaClaims.INTROSPECTION_RESPONSE_RFC_7662.equals(this.parser) ||
				TipologiaClaims.JSON_WEB_TOKEN_FOR_OAUTH2_ACCESS_TOKENS_RFC_9068.equals(this.parser) ||
				TipologiaClaims.OIDC_ID_TOKEN.equals(this.parser) ||
				TipologiaClaims.JSON_WEB_TOKEN_RFC_7519.equals(this.parser)) {
			tmpScopes = TokenUtilities.getClaimAsString(this.claims,Claims.INTROSPECTION_RESPONSE_RFC_7662_SCOPE);
		}
		else if(TipologiaClaims.GOOGLE.equals(this.parser)) {
			tmpScopes = TokenUtilities.getClaimAsString(this.claims,Claims.GOOGLE_CLAIMS_SCOPE);
		}
		else if(TipologiaClaims.MAPPING.equals(this.parser)) {
			List<String> claimNames = TokenUtilities.getClaims(this.parserConfig, Costanti.TOKEN_PARSER_SCOPE);
			List<String> tmpScopesList = TokenUtilities.getFirstClaimAsList(this.claims, claimNames);
			List<String> lNull = null;
			if(tmpScopesList!=null && !tmpScopesList.isEmpty()) {
				List<String> scopes = new ArrayList<>();
				for (String s : tmpScopesList) {
					List<String> tmp = readScope(s);
					if(tmp!=null && !tmp.isEmpty()) {
						for (String sTmp : tmp) {
							if(!scopes.contains(sTmp)) {
								scopes.add(sTmp);
							}
						}
					}
				}
				return scopes;
			}
			else {
				return lNull;
			}
		}
		
		return readScope(tmpScopes);
		
	}
	private List<String> readScope(String tmpScopes) {
		List<String> lNull = null;
		if(tmpScopes!=null) {
			String [] tmpArray = tmpScopes.split(" ");
			if(tmpArray!=null && tmpArray.length>0) {
				List<String> scopes = new ArrayList<>();
				for (int i = 0; i < tmpArray.length; i++) {
					scopes.add(tmpArray[i].trim());
				}
				return scopes;
			}
		}
		return lNull;
	}

	// ITokenUserInfoParser
	@Override
	public ITokenUserInfoParser getUserInfoParser() {
		return this.userInfoParser;
	}
}