ApiKeyUtilities.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.autenticazione;

import java.util.List;

import org.openspcoop2.core.config.ServizioApplicativo;
import org.openspcoop2.core.config.driver.db.DriverConfigurazioneDB;
import org.openspcoop2.core.id.IDServizioApplicativo;
import org.openspcoop2.core.id.IDSoggetto;
import org.openspcoop2.core.registry.Soggetto;
import org.openspcoop2.core.registry.driver.db.DriverRegistroServiziDB;
import org.openspcoop2.pdd.core.PdDContext;
import org.openspcoop2.pdd.core.connettori.InfoConnettoreIngresso;
import org.openspcoop2.utils.crypt.PasswordGenerator;
import org.openspcoop2.utils.io.Base64Utilities;

/**
 * ApiKeyUtilities
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class ApiKeyUtilities {

	public static String getKey(boolean apiKey,
			boolean header, boolean cookie, boolean queryParameter, 
			String nomeHeader, String nomeCookie, String nomeQueryParameter,
			InfoConnettoreIngresso infoConnettore, PdDContext pddContext, boolean throwException,
			StringBuilder fullCredential) throws AutenticazioneException {
		
		String tipo = apiKey ? "ApiKey" : "AppId";
		
		String key = null;
		
		// viene usato l'ordine della specifica
		
		if(queryParameter) {
			if(nomeQueryParameter==null && throwException) {
				throw new AutenticazioneException("Nome del parametro della query, da cui estrarre l'"+tipo+", non indicato");
			}
			if(nomeQueryParameter!=null && infoConnettore!=null && infoConnettore.getUrlProtocolContext()!=null) {
				key = infoConnettore.getUrlProtocolContext().getParameterFirstValue(nomeQueryParameter);
				if(key!=null && "".equals(key.trim())) {
					key = null;
				}
			}
		}
		if(key!=null) {
			if(fullCredential.length()>0) {
				fullCredential.append("\n");
			}
			fullCredential.append(tipo).append(" (query) '").append(nomeQueryParameter);
			if(!apiKey) { // l'api key non deve essere tracciata come per la password.
				fullCredential.append(": ").append(key);
			}
			fullCredential.append("'");
			return key;
		}
		
		if(header) {
			if(nomeHeader==null && throwException) {
				throw new AutenticazioneException("Nome dell'header, da cui estrarre l'"+tipo+", non indicato");
			}
			if(nomeHeader!=null && infoConnettore!=null && infoConnettore.getUrlProtocolContext()!=null) {
				key = infoConnettore.getUrlProtocolContext().getHeaderFirstValue(nomeHeader);
				if(key!=null && "".equals(key.trim())) {
					key = null;
				}
			}
		}
		if(key!=null) {
			if(fullCredential.length()>0) {
				fullCredential.append("\n");
			}
			fullCredential.append(tipo).append(" (http) '").append(nomeHeader);
			if(!apiKey) { // l'api key non deve essere tracciata come per la password.
				fullCredential.append(": ").append(key);
			}
			fullCredential.append("'");
			return key;
		}
		
		if(cookie) {
			if(nomeCookie==null && throwException) {
				throw new AutenticazioneException("Nome del cookie, da cui estrarre l'"+tipo+", non indicato");
			}
			if(nomeCookie!=null && infoConnettore!=null && infoConnettore.getUrlProtocolContext()!=null) {
				key = infoConnettore.getUrlProtocolContext().getCookieValue(nomeCookie);
				if(key!=null && "".equals(key.trim())) {
					key = null;
				}
			}
		}
		if(key!=null) {
			if(fullCredential.length()>0) {
				fullCredential.append("\n");
			}
			fullCredential.append(tipo).append(" (cookie) '").append(nomeCookie);
			if(!apiKey) { // l'api key non deve essere tracciata come per la password.
				fullCredential.append(": ").append(key);
			}
			fullCredential.append("'");
			return key;
		}
		
		if(throwException) {
			throw new AutenticazioneException(tipo+" non presente nella richiesta");
		}
			
		return null;
	}
	
	private static final String SOGGETTO_SEPARATOR = ".";	
	public static final String APPLICATIVO_SOGGETTO_SEPARATOR = "@";
	private static final int MAX_ALREADY_EXISTS = 5000;
	private static final String SEPARATOR_ALREADY_EXISTS = ".";
	
	public static String toAppId(String protocollo, IDSoggetto idSoggetto, boolean multipleApiKeys, DriverRegistroServiziDB driver) throws Exception {
		String appId = _toAppId(protocollo, idSoggetto);
		if(!_existsAppId(appId, multipleApiKeys, idSoggetto, driver)) {
			return appId;
		}
		for (int i = 2; i < MAX_ALREADY_EXISTS; i++) {
			IDSoggetto idSoggettoAlreadyExists = new IDSoggetto(idSoggetto.getTipo(), idSoggetto.getNome()+SEPARATOR_ALREADY_EXISTS+i);
			String appIdAlreadyExists = _toAppId(protocollo, idSoggettoAlreadyExists);
			if(!_existsAppId(appIdAlreadyExists, multipleApiKeys, idSoggetto, driver)) {
				return appIdAlreadyExists;
			}
		}
		throw new Exception("Generazione appId univoco non riuscita dopo "+MAX_ALREADY_EXISTS+" tentativi");
	}
	private static String _toAppId(String protocollo, IDSoggetto idSoggetto) throws Exception {
		// non va bene perchè due nomi uguali di due protocolli differenti, vengono identici.
		//return org.openspcoop2.protocol.engine.utils.NamingUtils.getLabelSoggetto(protocollo, idSoggetto.getTipo(), idSoggetto.getNome());	
		if(idSoggetto==null || idSoggetto.getTipo()==null || idSoggetto.getNome()==null) {
			throw new Exception("Identificativo soggetto non definito");
		}
		return idSoggetto.getNome()+SOGGETTO_SEPARATOR+idSoggetto.getTipo();
	}
	private static boolean _existsAppId(String appId, boolean multipleApiKeys, IDSoggetto idSoggetto, DriverRegistroServiziDB driver) throws Exception {
		Soggetto soggetto = driver.soggettoWithCredenzialiApiKey(appId, multipleApiKeys);
		if(soggetto==null) {
			return false;
		}
		boolean isSame = false;
		if(soggetto.getTipo().equals(idSoggetto.getTipo()) &&
				soggetto.getNome().equals(idSoggetto.getNome()) ) {
			isSame = true;
		}
		return !isSame;
	}
	
	public static String toAppId(String protocollo, IDServizioApplicativo idSA, boolean multipleApiKeys, DriverConfigurazioneDB driver) throws Exception {
		String appId = _toAppId(protocollo, idSA.getNome(), idSA.getIdSoggettoProprietario());
		if(!_existsAppId(appId, multipleApiKeys, idSA, driver)) {
			return appId;
		}
		for (int i = 2; i < MAX_ALREADY_EXISTS; i++) {
			String appIdAlreadyExists = _toAppId(protocollo, idSA.getNome()+SEPARATOR_ALREADY_EXISTS+i, idSA.getIdSoggettoProprietario());
			if(!_existsAppId(appIdAlreadyExists, multipleApiKeys, idSA, driver)) {
				return appIdAlreadyExists;
			}
		}
		throw new Exception("Generazione appId univoco non riuscita dopo "+MAX_ALREADY_EXISTS+" tentativi");
	}
	private static String _toAppId(String protocollo, String nomeSA, IDSoggetto idSoggetto) throws Exception {
		return nomeSA+APPLICATIVO_SOGGETTO_SEPARATOR+ _toAppId(protocollo, idSoggetto);
	}
	private static boolean _existsAppId(String appId, boolean multipleApiKeys,  IDServizioApplicativo idSA, DriverConfigurazioneDB driver) throws Exception {
		List<ServizioApplicativo> saList = driver.servizioApplicativoWithCredenzialiApiKeyList(appId, multipleApiKeys);
		if (saList==null || saList.isEmpty()) {
			return false;
		}
		boolean isSame = false;
		for (ServizioApplicativo servizioApplicativo : saList) {
			if(!servizioApplicativo.getNome().equals(idSA.getNome())) {
				continue;
			}
			if(!servizioApplicativo.getTipoSoggettoProprietario().equals(idSA.getIdSoggettoProprietario().getTipo())) {
				continue;
			}
			if(!servizioApplicativo.getNomeSoggettoProprietario().equals(idSA.getIdSoggettoProprietario().getNome())) {
				continue;
			}
			isSame = true;
			break;
		}
		return !isSame;
	}
			
	private static final String PREFIX_SEPARATOR_API_KEY = ".";
	private static final String PREFIX_SEPARATOR_API_KEY_REGEXP = "\\.";
	
	private static String getPrefixForApiKey(String protocollo, IDSoggetto idSoggetto, DriverRegistroServiziDB driver) throws Exception {
		boolean multipleApiKeys = false;
		String appId = toAppId(protocollo, idSoggetto, multipleApiKeys, driver);
		return Base64Utilities.encodeAsString(appId.getBytes())+PREFIX_SEPARATOR_API_KEY;
	}
	public static ApiKey newApiKey(String protocollo, IDSoggetto idSoggetto, int length, DriverRegistroServiziDB driver) throws Exception {
		ApiKey apiKey = new ApiKey();
		String password = getPassword(length);
		String apiKeyToken = getPrefixForApiKey(protocollo, idSoggetto, driver)+Base64Utilities.encodeAsString(password.getBytes());
		apiKey.setApiKey(apiKeyToken);
		apiKey.setPassword(password);
		return apiKey;
	}
	
	private static String getPrefixForApiKey(String protocollo, IDServizioApplicativo idSA, DriverConfigurazioneDB driver) throws Exception {
		boolean multipleApiKeys = false;
		String appId = toAppId(protocollo, idSA, multipleApiKeys, driver);
		return Base64Utilities.encodeAsString(appId.getBytes())+PREFIX_SEPARATOR_API_KEY;
	}
	public static ApiKey newApiKey(String protocollo, IDServizioApplicativo idSA, int length, DriverConfigurazioneDB driver) throws Exception {
		ApiKey apiKey = new ApiKey();
		String password = getPassword(length);
		String apiKeyToken = getPrefixForApiKey(protocollo, idSA, driver)+Base64Utilities.encodeAsString(password.getBytes());
		apiKey.setApiKey(apiKeyToken);
		apiKey.setPassword(password);
		return apiKey;
	}
	
	public static String encodeApiKey(String appId, String password) throws Exception {
		return Base64Utilities.encodeAsString(appId.getBytes())+
			PREFIX_SEPARATOR_API_KEY+
			Base64Utilities.encodeAsString(password.getBytes());
	}
	
	public static String[] decodeApiKey(String apiKeyBase64) throws Exception {
		if(!apiKeyBase64.contains(PREFIX_SEPARATOR_API_KEY)) {
			throw new Exception("Formato non corretto");
		}
		String [] tmp = apiKeyBase64.split(PREFIX_SEPARATOR_API_KEY_REGEXP);
		if(tmp==null || tmp.length!=2){
			throw new Exception("Formato non corretto (.)");
		}
		try {
			tmp[0] = new String(Base64Utilities.decode(tmp[0]));
		}catch(Exception e) {
			throw new Exception("Formato non corretto (appId)",e);
		}
		try {
			tmp[1] = new String(Base64Utilities.decode(tmp[1]));
		}catch(Exception e) {
			throw new Exception("Formato non corretto (appKey)",e);
		}
		return tmp;
	}
	
	
	public static ApiKey newMultipleApiKey(int length) throws Exception {
		ApiKey apiKey = new ApiKey();
		String password = getPassword(length);
		String apiKeyToken = encodeMultipleApiKey(password);
		apiKey.setApiKey(apiKeyToken);
		apiKey.setPassword(password);
		return apiKey;
	}
	
	public static String decodeMultipleApiKey(String apiKeyBase64) throws Exception {
		return new String(Base64Utilities.decode(apiKeyBase64));
	}
	public static String encodeMultipleApiKey(String password) throws Exception {
		return Base64Utilities.encodeAsString(password.getBytes());
	}
	
	private static String getPassword(int length) throws Exception {
		PasswordGenerator pwdGenerator = PasswordGenerator.DEFAULT;
		return pwdGenerator.generate(length);
	}
}