diff --git a/src/server/ch/epfl/lca1/medco/StandardQuery.java b/src/server/ch/epfl/lca1/medco/StandardQuery.java index 76fae1a..74fb42e 100644 --- a/src/server/ch/epfl/lca1/medco/StandardQuery.java +++ b/src/server/ch/epfl/lca1/medco/StandardQuery.java @@ -1,244 +1,244 @@ package ch.epfl.lca1.medco; import ch.epfl.lca1.medco.i2b2.crc.I2B2CRCCell; import ch.epfl.lca1.medco.i2b2.crc.I2B2QueryRequest; import ch.epfl.lca1.medco.i2b2.crc.I2B2QueryResponse; import ch.epfl.lca1.medco.i2b2.pm.I2B2PMCell; import ch.epfl.lca1.medco.i2b2.pm.UserInformation; import ch.epfl.lca1.medco.unlynx.UnlynxClient; import ch.epfl.lca1.medco.util.Constants; import ch.epfl.lca1.medco.util.Logger; import ch.epfl.lca1.medco.util.Timers; import ch.epfl.lca1.medco.util.exceptions.MedCoError; import ch.epfl.lca1.medco.util.exceptions.MedCoException; import edu.harvard.i2b2.common.exception.I2B2Exception; import edu.harvard.i2b2.crc.datavo.pm.RoleType; import edu.harvard.i2b2.crc.datavo.setfinder.query.PanelType; import edu.harvard.i2b2.crc.datavo.setfinder.query.QueryDefinitionType; import org.javatuples.Pair; import sun.rmi.runtime.Log; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; /** * Implements the MedCo v0.1 query workflow from within the i2b2 hive. */ public class StandardQuery { // todo: make more generic the use of unlynx (as a cryptographic engine using specific protocols) // todo: implement user authentication & authorization // todo: implement differential privacy /** * The incoming query request, in i2b2 CRC format. */ private I2B2QueryRequest queryRequest; /** * Connection to the i2b2 CRC cell (data repository). */ private I2B2CRCCell crcCell; /** * Connection to the i2b2 PM cell (hive data and user management). */ private I2B2PMCell pmCell; /** * Connection to Unlynx (cryptographic engine). */ private UnlynxClient unlynxClient; /** * All the timers of the query. */ private Timers timers; /** * * @param request the incoming query request to execute * @param unlynxBinPath path to the Unlynx binary to call * @param unlynxGroupFilePath public key of the Unlynx collective authority * @param unlynxDebugLevel debug level to pass to Unlynx * @param unlynxEntryPointIdx index (within the group file) of the collective authority node attached to the current instance * @param unlynxProofsFlag flag for proof usage in Unlynx (dramatically decrease performances if true) * @param unlynxTimeoutSeconds timeout value for Unlynx * @param crcCellUrl URL of the i2b2 CRC cell * @param pmCellUrl URL of the i2b2 PM cell */ public StandardQuery(I2B2QueryRequest request, String unlynxBinPath, String unlynxGroupFilePath, int unlynxDebugLevel, int unlynxEntryPointIdx, int unlynxProofsFlag, long unlynxTimeoutSeconds, String crcCellUrl, String pmCellUrl) { this.queryRequest = request; unlynxClient = new UnlynxClient(unlynxBinPath, unlynxGroupFilePath, unlynxDebugLevel, unlynxEntryPointIdx, unlynxProofsFlag, unlynxTimeoutSeconds); crcCell = new I2B2CRCCell(crcCellUrl, queryRequest.getMessageHeader()); pmCell = new I2B2PMCell(pmCellUrl, queryRequest.getMessageHeader()); timers = new Timers(); } /** * Implements the high-level step-by-step logic of the MedCo query. * * @return the query answer in CRC XML format, ready to send back to the requester * * @throws MedCoException if the query fails for a MedCo-specific reason * @throws I2B2Exception if the query fails for an i2b2-specific reason */ public I2B2QueryResponse executeQuery() throws MedCoException, I2B2Exception { timers.resetTimers(); timers.get("overall").start(); // get user information (auth., privacy budget, authorizations, public key) // todo: get and check budget query / user // todo: get user permissions timers.get("steps").start("User information retrieval"); UserInformation user = pmCell.getUserInformation(queryRequest.getMessageHeader()); Logger.warn("Riccardo: created user: " + user.getUsername()); if (!user.isAuthenticated()) { Logger.warn("Authentication failed for user " + user.getUsername()); // todo: proper auth failed response return null; } RoleType userRole = user.checkUserRolePermission(queryRequest); - Logger.warn("Riccardo: user role: " + userRole.getRole()); + Logger.warn("Riccardo: user role: "); if (userRole == null) { Logger.warn("User not allowed to perform the query."); return null; //TODO return proper error } timers.get("steps").stop(); // retrieve the encrypted query terms timers.get("steps").start("Query parsing/splitting"); List encryptedQueryItems = getEncryptedQueryTerms(); timers.get("steps").stop(); // intercept test query from SHRINE and bypass unlynx if (encryptedQueryItems.contains(Constants.CONCEPT_NAME_TEST_FLAG)) { Logger.info("Intercepted SHRINE status query (" + queryRequest.getQueryName() + ")."); replaceEncryptedQueryTerms(encryptedQueryItems); return crcCell.queryRequest(queryRequest); } // query unlynx to tag the query terms timers.get("steps").start("Query tagging"); List taggedItems = unlynxClient.computeDistributedDetTags(queryRequest.getQueryName(), encryptedQueryItems); timers.addAdditionalTimes(unlynxClient.getLastTimingMeasurements()); timers.get("steps").stop(); // replace the query terms, query i2b2 with the original clear query terms + the tagged ones timers.get("steps").start("i2b2 query"); replaceEncryptedQueryTerms(taggedItems); queryRequest.setOutputTypes(new String[]{"PATIENTSET", "PATIENT_COUNT_XML"}); // TODO ???? I2B2QueryResponse i2b2Response = crcCell.queryRequest(queryRequest); timers.get("steps").stop(); // retrieve the patient set, including the encrypted dummy flags timers.get("steps").start("i2b2 patient set retrieval"); Pair, List> patientSet = crcCell.queryForPatientSet(i2b2Response.getPatientSetId(), true); Logger.warn("Riccardo: patient set " + patientSet.getValue0() + " " + patientSet.getValue1()); timers.get("steps").stop(); String aggResult; switch (userRole.getRole()) { //TODO all the cases case "DATA_SITE": aggResult = unlynxClient.aggregateData(queryRequest.getQueryName(), user.getUserPublicKey(), patientSet.getValue1()); timers.addAdditionalTimes(unlynxClient.getLastTimingMeasurements()); break; case "DATA_SITE_OBF": case "DATA_TOT_OBF": // Hash the patient count Logger.warn("Riccardo: here will have the hash and table"); // Hashing the patientSet MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("SHA-256"); } catch (Exception NoSuchAlgorithmException) { return null; //TODO error } messageDigest.update(String.join("", patientSet.getValue0()).getBytes()); String hashPatientSet = new String(messageDigest.digest()); Logger.warn("Riccardo: hash patient set: " + hashPatientSet); case "DATA_IND": case "DATA_SITE_SHF": case "DATA_SITE_OBF_SHF": case "DATA_TOT": default: throw new MedCoError("Query type not supported yet."); } i2b2Response.resetResultInstanceListToEncryptedCountOnly(); timers.get("overall").stop(); i2b2Response.setQueryResults(user.getUserPublicKey(), aggResult, timers.generateFullReport()); Logger.info("MedCo query successful (" + queryRequest.getQueryName() + ")."); return i2b2Response; } /** * Replace within the queryRequest, in top-bottom order, the encrypted query terms with their tagged equivalent. * * @param taggedQueryTerms the tagged query terms to use for replacement. */ private void replaceEncryptedQueryTerms(List taggedQueryTerms) { QueryDefinitionType qd = queryRequest.getQueryDefinition(); int encTermCount = 0; for (int p = 0; p < qd.getPanel().size(); p++) { PanelType panel = qd.getPanel().get(p); int nbItems = panel.getItem().size(); for (int i = 0; i < nbItems; i++) { // replace encrypted item with its tagged version Matcher medcoKeyMatcher = Constants.REGEX_QUERY_KEY_ENC.matcher(panel.getItem().get(i).getItemKey()); if (medcoKeyMatcher.matches()) { Logger.debug("Replacing " + panel.getItem().get(i).getItemKey() + " by " + taggedQueryTerms.get(encTermCount) + " on (panel=" + p + ", item=" + i + ")" ); panel.getItem().get(i).setItemKey(Constants.CONCEPT_PATH_TAGGED_PREFIX + taggedQueryTerms.get(encTermCount++) + "\\"); } } } // check the provided taggedQueryTerms match the number of encrypted terms if (encTermCount != taggedQueryTerms.size()) { Logger.warn("Mismatch in provided number of tagged items (" + taggedQueryTerms.size() + ") and number of encrypted items in query (" + encTermCount + ")"); } } /** * @return the list of encrypted terms from the queryRequest in top-bottom order. */ private List getEncryptedQueryTerms() { QueryDefinitionType qd = queryRequest.getQueryDefinition(); List extractedItems = new ArrayList<>(); for (int p = 0; p < qd.getPanel().size(); p++) { PanelType panel = qd.getPanel().get(p); int nbItems = panel.getItem().size(); for (int i = 0; i < nbItems; i++) { // check if item is clear or encrypted, extract and generate predicate if yes Matcher medcoKeyMatcher = Constants.REGEX_QUERY_KEY_ENC.matcher(panel.getItem().get(i).getItemKey()); if (medcoKeyMatcher.matches()) { extractedItems.add(medcoKeyMatcher.group(1)); Logger.debug("Returned item " + extractedItems.get(extractedItems.size() - 1) + "; panel=" + p + ", item=" + i); } } } Logger.info("Returned " + extractedItems.size() + " encrypted query terms for query " + queryRequest.getQueryName()); return extractedItems; } }