diff --git a/Docker/pg-Dockerfile b/Docker/pg-Dockerfile new file mode 100644 index 00000000..642654ac --- /dev/null +++ b/Docker/pg-Dockerfile @@ -0,0 +1,7 @@ +FROM ghcr.io/supabase/postgres:17.11.0.004 + +ENV POSTGRES_DB '' +ENV POSTGRES_USER '' +ENV POSTGRES_PASSWORD '' + +COPY z-load-extensions.sh /docker-entrypoint-initdb.d/ diff --git a/config/postgres-config/postgresql.conf b/Docker/postgresql.conf similarity index 100% rename from config/postgres-config/postgresql.conf rename to Docker/postgresql.conf diff --git a/Docker/vault-key.sh b/Docker/vault-key.sh new file mode 100755 index 00000000..425e08eb --- /dev/null +++ b/Docker/vault-key.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -euo pipefail + +KEY_FILE=./pgsodium_root.key + +if [[ ! -f "${KEY_FILE}" ]]; then + head -c 32 /dev/urandom | od -A n -t x1 | tr -d ' \n' > "${KEY_FILE}" +fi diff --git a/Docker/z-load-extensions.sh b/Docker/z-load-extensions.sh new file mode 100644 index 00000000..9b826d12 --- /dev/null +++ b/Docker/z-load-extensions.sh @@ -0,0 +1,9 @@ +# Named "z-load-extensions.sh" because postgres docker runs these scripts in alphabetical order, +# and it needs to run after supabase's other scripts have run. + +createdb --username="$POSTGRES_USER" "user_transactions" + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" -d user_transactions <(T field) { + public String toString() { + return "SensitiveInfo[value=***]"; + } +} diff --git a/EfspCommons/src/main/java/edu/suffolk/litlab/efsp/stdlib/StdLib.java b/EfspCommons/src/main/java/edu/suffolk/litlab/efsp/stdlib/StdLib.java index 1fb22651..361219bf 100644 --- a/EfspCommons/src/main/java/edu/suffolk/litlab/efsp/stdlib/StdLib.java +++ b/EfspCommons/src/main/java/edu/suffolk/litlab/efsp/stdlib/StdLib.java @@ -17,4 +17,12 @@ public static Optional GetEnv(String envVarName) { public static boolean exists(String val) { return val != null && !val.isBlank(); } + + public static String chooseNotBlank(String val, String val2) { + if (exists(val)) { + return val; + } else { + return val2; + } + } } diff --git a/README.md b/README.md index 81455cdc..52b5dfaf 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ These instructions are written for Linux (specifically Ubuntu 22.04), steps for ```bash git clone https://github.com/SuffolkLITLab/EfileProxyServer cd EfileProxyServer + # Need to run this for the database setup to work + ./Docker/run_vault.sh docker compose -f dev-compose.yml up --watch # For a development build docker compose build && docker compose up -d # For a production-like build ``` @@ -73,4 +75,4 @@ different modules for better defined interfaces. Those modules include: * [TylerEfmClient](TylerEfmClient/README.md) * [TylerEcf4](TylerEcf4/README.md) * [TylerEcf5](TylerEcf5/README.md) -* [TrueFilingEcf4](TrueFilingEcf4/README.md) \ No newline at end of file +* [TrueFilingEcf4](TrueFilingEcf4/README.md) diff --git a/docker-compose.yml b/docker-compose.yml index 52a9d9d1..dd2bc1a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,12 +17,15 @@ services: limits: memory: 4G db: - image: "postgres:15" + build: + context: Docker + dockerfile: pg-Dockerfile shm_size: 256m env_file: .env volumes: - data-volume:/var/lib/postgresql/data - - ./config/postgres-config:/etc/postgresql + # run Docker/vault-key.sh before starting the docker compose database + - ./Docker/pgsodium_root.key:/etc/postgresql-custom/pgsodium_root.key:Z volumes: data-volume: diff --git a/proxyserver/README.md b/proxyserver/README.md index 98068bf5..bc41b689 100644 --- a/proxyserver/README.md +++ b/proxyserver/README.md @@ -13,7 +13,7 @@ This server can also be run directly with java, for remote debugging or speed of * Edit your `.env` file, particularly `POSTGRES_URL` and `POSTGRES_PORT`, to match how you will be running the db (`POSTGRES_URL` can be `localhost` if just running it locally / through a docker container). * If you still want to use docker for this, run - `docker run -p 5432:5432 --env-file .env --volume efileproxyserver_data-volume:/var/lib/postgresql/data postgres:15`. + `docker run -p 5432:5432 --env-file .env --volume efileproxyserver_data-volume:/var/lib/postgresql/data ghcr.io/supabase/postgres:15.1.1.58`. * `mvn package` to build the output JAR artifact. * `cd proxyserver` (if not already there), and then `./run_server.sh` @@ -35,4 +35,4 @@ If using VS Code, add the below configuration to your `.vscode/launch.json`: ... ] } -``` \ No newline at end of file +``` diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/DatabaseVersion.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/DatabaseVersion.java index b88a52af..4e29639e 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/DatabaseVersion.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/DatabaseVersion.java @@ -20,11 +20,19 @@ * going back to extremely old versions of the code, this class should be completely self contained, * only using SQL queries from here. * + *

To add a new version: + * + *

    + *
  1. Increment CURRENT_VERSION + *
  2. Add an else-if to the end of updateDatabase() for the new version. + *
  3. Write your actual upgrade function (upgradeFromYYToZZ()). + *
+ * * @author brycew */ public class DatabaseVersion { - static final int CURRENT_VERSION = 11; + static final int CURRENT_VERSION = 12; private static Logger log = LoggerFactory.getLogger(DatabaseVersion.class); private final Connection codeConn; private final Connection userConn; @@ -152,6 +160,8 @@ private boolean updateDatabase(int onDiskVersion) throws NoSuchAlgorithmExceptio update9To10(); } else if (onDiskVersion == 10) { update10To11(); + } else if (onDiskVersion == 11) { + update11To12(); } setSchemaVersion(onDiskVersion + 1); userConn.commit(); @@ -608,4 +618,30 @@ public void update10To11() throws SQLException { } codeConn.commit(); } + + public void update11To12() throws SQLException { + final String alterMsgTable = + """ + ALTER TABLE message_settings + ADD COLUMN sendgrid_vault_id uuid, + ADD COLUMN is_default boolean DEFAULT true, + DROP CONSTRAINT message_settings_pkey + """; + final String newPkey = + """ + ALTER TABLE message_settings + ADD PRIMARY KEY (server_id, from_email); + """; + final String newSubmittedColumn = + """ + ALTER TABLE submitted_filings + ADD COLUMN from_email text + """; + try (Statement st = userConn.createStatement()) { + st.executeUpdate(alterMsgTable); + st.executeUpdate(newPkey); + st.executeUpdate(newSubmittedColumn); + } + userConn.commit(); + } } diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/MessageSettingsDatabase.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/MessageSettingsDatabase.java index 3c22b64e..4412a331 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/MessageSettingsDatabase.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/MessageSettingsDatabase.java @@ -44,8 +44,8 @@ public void createTablesIfAbsent() throws SQLException { String createQuery = """ CREATE TABLE message_settings ( - "server_id" uuid PRIMARY KEY, "from_email" text, "subject_line" text, - "email_template" text, "email_confirmation" text, "confirmation_subject_line" text)\ + "server_id" uuid, "from_email" text, "is_default" boolean DEFAULT true, "sendgrid_vault_id" uuid, "subject_line" text, + "email_template" text, "email_confirmation" text, "confirmation_subject_line" text, PRIMARY KEY(server_id, from_email))\ """; try (Statement createSt = conn.createStatement()) { int retVal = createSt.executeUpdate(createQuery); @@ -60,17 +60,19 @@ CREATE TABLE message_settings ( public void updateTable(MessageInfo info) throws SQLException { updateTable( - info.serverId, - info.fromEmail, - info.subjectLine, - info.emailResponseTemplate, - info.confirmationSubjectLine, - info.emailConfirmation); + info.serverId(), + info.fromEmail(), + false, + info.subjectLine(), + info.emailResponseTemplate(), + info.confirmationSubjectLine(), + info.emailConfirmation()); } public void updateTable( UUID serverId, String toEmail, + boolean isDefault, String subjectLine, String emailTemplate, String confirmationSubjectLine, @@ -84,24 +86,25 @@ public void updateTable( String insertIntoTable = """ INSERT INTO message_settings ( - "server_id", "from_email", "subject_line", "email_template", "confirmation_subject_line", "email_confirmation" + "server_id", "from_email", "is_default", "subject_line", "email_template", "confirmation_subject_line", "email_confirmation" ) VALUES ( - ?, ?, ?, ?, ?, ?) ON CONFLICT (server_id) DO UPDATE SET from_email=?, subject_line=?, + ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (server_id, from_email) DO UPDATE SET is_default=?, subject_line=?, email_template=?, confirmation_subject_line=?, email_confirmation=? """; try (PreparedStatement insertSt = conn.prepareStatement(insertIntoTable)) { insertSt.setObject(1, serverId); insertSt.setString(2, toEmail); - insertSt.setString(3, subjectLine); - insertSt.setString(4, emailTemplate); - insertSt.setString(5, confirmationSubjectLine); - insertSt.setString(6, emailConfirmation); - insertSt.setString(7, toEmail); - insertSt.setString(8, subjectLine); - insertSt.setString(9, emailTemplate); - insertSt.setString(10, confirmationSubjectLine); - insertSt.setString(11, emailConfirmation); + insertSt.setBoolean(3, isDefault); + insertSt.setString(4, subjectLine); + insertSt.setString(5, emailTemplate); + insertSt.setString(6, confirmationSubjectLine); + insertSt.setString(7, emailConfirmation); + insertSt.setBoolean(8, isDefault); + insertSt.setString(9, subjectLine); + insertSt.setString(10, emailTemplate); + insertSt.setString(11, confirmationSubjectLine); + insertSt.setString(12, emailConfirmation); insertSt.executeUpdate(); } } @@ -114,19 +117,24 @@ public Optional findMessageInfo(UUID serverId) { String query = """ - SELECT server_id, from_email, subject_line, email_template, confirmation_subject_line, + SELECT server_id, from_email, sendgrid_vault_id, subject_line, email_template, confirmation_subject_line, email_confirmation FROM message_settings - WHERE server_id=?\ + WHERE server_id=? AND is_default=true\ """; try (PreparedStatement st = conn.prepareStatement(query)) { st.setObject(1, serverId); ResultSet rs = st.executeQuery(); if (rs.next()) { + String sendgridKey = null; + if (rs.getObject(3) != null) { + sendgridKey = findSendgridKey((UUID) rs.getObject(3)).orElse(null); + } MessageInfo info = new MessageInfo( (UUID) rs.getObject(1), rs.getString(2), + sendgridKey, rs.getString(3), rs.getString(4), rs.getString(5), @@ -140,4 +148,72 @@ public Optional findMessageInfo(UUID serverId) { log.warn("No settings for {}", serverId); return Optional.empty(); } + + public Optional findMessageInfo(UUID serverId, String fromEmail) { + if (conn == null) { + log.error("Connection in addToTable wasn't open yet!"); + return Optional.empty(); + } + + String query = + """ + SELECT server_id, from_email, sendgrid_vault_id, subject_line, email_template, confirmation_subject_line, + email_confirmation + FROM message_settings + WHERE server_id=? AND from_email=?\ + """; + try (PreparedStatement st = conn.prepareStatement(query)) { + st.setObject(1, serverId); + st.setString(2, fromEmail); + ResultSet rs = st.executeQuery(); + if (rs.next()) { + String sendgridKey = null; + if (rs.getObject(3) != null) { + sendgridKey = findSendgridKey((UUID) rs.getObject(3)).orElse(null); + } + MessageInfo info = + new MessageInfo( + (UUID) rs.getObject(1), + rs.getString(2), + sendgridKey, + rs.getString(3), + rs.getString(4), + rs.getString(5), + rs.getString(6)); + return Optional.of(info); + } + } catch (SQLException e) { + log.error("Exception getting message settings", e); + return Optional.empty(); + } + log.warn("No settings for {} with from email {}, falling back to default settings", serverId); + return findMessageInfo(serverId); + } + + private Optional findSendgridKey(UUID vaultId) { + if (conn == null) { + log.error("Connection in addToTable wasn't open yet!"); + return Optional.empty(); + } + + String query = + """ + SELECT id, decrypted_secret + FROM vault.decrypted_secrets + WHERE id=?\ + """; + try (PreparedStatement st = conn.prepareStatement(query)) { + st.setObject(1, vaultId); + ResultSet rs = st.executeQuery(); + if (rs.next()) { + if (rs.getString(2) != null) { + return Optional.of(rs.getString(2)); + } + } + return Optional.empty(); + } catch (SQLException e) { + log.error("Exception getting message settings", e); + return Optional.empty(); + } + } } diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/UserDatabase.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/UserDatabase.java index 3b2d55e4..17f37c95 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/UserDatabase.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/UserDatabase.java @@ -60,7 +60,8 @@ CREATE TABLE submitted_filings ( "neutral_msg_template" text, "neutral_msg_subject" text, "case_title" text, - "envelope_id" text)\ + "envelope_id" text, + "from_email" text)\ """; try (Statement createSt = conn.createStatement()) { log.info("Full statement: {}", createQuery); @@ -140,12 +141,13 @@ INSERT INTO submitted_filings ( "neutral_msg_template", "neutral_msg_subject", "case_title", - "envelope_id" + "envelope_id", + "from_email" ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, ?, ?, ?)\ + ?, ?, ?, ?, ?, ?, ?, ?, ?)\ """; try (PreparedStatement insertSt = conn.prepareStatement(insertIntoTable)) { insertSt.setObject(1, filingPartyId); @@ -167,6 +169,7 @@ INSERT INTO submitted_filings ( insertSt.setString(16, tmpls.neutralSubject()); insertSt.setString(17, caseTitle); insertSt.setString(18, envelopeId); + insertSt.setString(19, tmpls.fromEmail()); insertSt.executeUpdate(); } } diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/MessageInfo.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/MessageInfo.java index 9ebdb1e2..367ee680 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/MessageInfo.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/MessageInfo.java @@ -1,35 +1,89 @@ package edu.suffolk.litlab.efsp.db.model; +import static edu.suffolk.litlab.efsp.stdlib.StdLib.chooseNotBlank; + import com.fasterxml.jackson.databind.JsonNode; +import edu.suffolk.litlab.efsp.stdlib.SensitiveInfo; +import java.util.Optional; import java.util.UUID; -public class MessageInfo { - public UUID serverId; - public String fromEmail; - public String subjectLine; - public String emailResponseTemplate; - public String confirmationSubjectLine; - public String emailConfirmation; +public record MessageInfo( + UUID serverId, + String fromEmail, + Optional> sendgridApiKey, + String subjectLine, + String emailResponseTemplate, + String confirmationSubjectLine, + String emailConfirmation) { public MessageInfo( UUID serverId, String fromEmail, + Optional> sendgridApiKey, String subjectLine, String emailResponseTemplate, String confirmationSubjectLine, String emailConfirmation) { this.serverId = serverId; this.fromEmail = fromEmail; + this.sendgridApiKey = sendgridApiKey; this.subjectLine = subjectLine; this.emailResponseTemplate = emailResponseTemplate; this.emailConfirmation = emailConfirmation; this.confirmationSubjectLine = confirmationSubjectLine; } + public MessageInfo( + UUID serverId, + String fromEmail, + String sendgridApiKey, + String subjectLine, + String emailResponseTemplate, + String confirmationSubjectLine, + String emailConfirmation) { + this( + serverId, + fromEmail, + Optional.ofNullable(sendgridApiKey).map(key -> new SensitiveInfo<>(key)), + subjectLine, + emailResponseTemplate, + confirmationSubjectLine, + emailConfirmation); + } + + public MessageInfo( + UUID serverId, + String fromEmail, + String subjectLine, + String emailResponseTemplate, + String confirmationSubjectLine, + String emailConfirmation) { + this( + serverId, + fromEmail, + Optional.empty(), + subjectLine, + emailResponseTemplate, + confirmationSubjectLine, + emailConfirmation); + } + + public MessageInfo overlayOn(MessageInfo newInfo) { + return new MessageInfo( + serverId, + chooseNotBlank(fromEmail, newInfo.fromEmail()), + sendgridApiKey.or(() -> newInfo.sendgridApiKey()), + chooseNotBlank(subjectLine, newInfo.subjectLine()), + chooseNotBlank(emailResponseTemplate, newInfo.emailResponseTemplate()), + chooseNotBlank(confirmationSubjectLine, newInfo.confirmationSubjectLine()), + chooseNotBlank(emailConfirmation, newInfo.emailConfirmation())); + } + public MessageInfo(JsonNode node) { this( (node.has("server_id")) ? UUID.fromString(node.get("server_id").asText(null)) : null, (node.has("from_email")) ? node.get("from_email").asText(null) : null, + (String) null, (node.has("subject_line")) ? node.get("subject_line").asText(null) : null, (node.has("email_template")) ? node.get("email_template").asText(null) : null, (node.has("email_confirmation_subject")) diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/Transaction.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/Transaction.java index dc5bfeac..075f335f 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/Transaction.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/db/model/Transaction.java @@ -20,6 +20,7 @@ public class Transaction { public String neutralMsgSubject; public UUID transactionId; public UUID serverId; + public String fromEmail; public String apiKeyUsed; public String caseTypeName; public String courtId; @@ -32,7 +33,8 @@ public class Transaction { SELECT name, user_id, phone_number, email, transaction_id, server_id, api_key_used, casetype, court_id, submitted, accepted_msg_template, accepted_msg_subject, rejected_msg_template, rejected_msg_subject, - neutral_msg_template, neutral_msg_subject, case_title, envelope_id + neutral_msg_template, neutral_msg_subject, case_title, envelope_id, + from_email FROM submitted_filings WHERE transaction_id = ?\ """; @@ -61,6 +63,7 @@ public static Transaction fromResults(ResultSet rs) throws SQLException { trans.neutralMsgSubject = rs.getString(16); trans.caseTitle = rs.getString(17); trans.envelopeId = rs.getString(18); + trans.fromEmail = rs.getString(19); return trans; } } diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/docassemble/FilingInformationDocassembleJacksonDeserializer.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/docassemble/FilingInformationDocassembleJacksonDeserializer.java index a4be20e0..53738646 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/docassemble/FilingInformationDocassembleJacksonDeserializer.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/docassemble/FilingInformationDocassembleJacksonDeserializer.java @@ -458,7 +458,8 @@ public FilingInformation fromNode(JsonNode node, InfoCollector collector) throws getStringDefault(node, "rejected_contents", ""), getStringDefault(node, "rejected_subject", ""), getStringDefault(node, "neutral_contents", ""), - getStringDefault(node, "neutral_subject", "")); + getStringDefault(node, "neutral_subject", ""), + getStringDefault(node, "from_email", "")); entities.setEmailTemplates(emailTemplates); diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/model/EmailTemplates.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/model/EmailTemplates.java index c6489a1b..ad2e48da 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/model/EmailTemplates.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/model/EmailTemplates.java @@ -1,5 +1,7 @@ package edu.suffolk.litlab.efsp.model; +import java.util.Optional; + public record EmailTemplates( String confirmationTemplate, String confirmationSubject, @@ -8,9 +10,18 @@ public record EmailTemplates( String rejectedTemplate, String rejectedSubject, String neutralTemplate, - String neutralSubject) { + String neutralSubject, + String fromEmail) { public EmailTemplates() { - this("", "", "", "", "", "", "", ""); + this("", "", "", "", "", "", "", "", ""); + } + + public Optional checkFromEmail() { + if (fromEmail == null || fromEmail.isBlank()) { + return Optional.empty(); + } else { + return Optional.of(fromEmail); + } } } diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/services/MessageSettingsService.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/services/MessageSettingsService.java index ce41d04c..df708d87 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/services/MessageSettingsService.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/services/MessageSettingsService.java @@ -20,6 +20,7 @@ import jakarta.ws.rs.core.SecurityContext; import java.sql.SQLException; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,10 +51,11 @@ public Response getAll() { @Path("/settings") @NeedsAuthorization public Response getMsgSettings(@Context SecurityContext security) { + // TODO: add from_email here if anyone wants to query specific ones EfspSecurityContext efspSecurity = (EfspSecurityContext) security; MDC.put(MDCWrappers.OPERATION, "MessageSettingsService.getMsgSettings"); OrgMessageSender orgMsg = new OrgMessageSender(mdSupplier, null); - MessageInfo info = orgMsg.getSettings(efspSecurity.getServerId()); + MessageInfo info = orgMsg.getSettings(efspSecurity.getServerId(), Optional.empty()); return Response.ok(info).build(); } @@ -82,10 +84,7 @@ public Response setMsgSettings(@Context SecurityContext security, String newInfo MessageInfo existingInfo = md.findMessageInfo(serverId) .orElse(new MessageInfo(serverId, null, null, null, null, null)); - existingInfo.emailResponseTemplate = newInfo.emailResponseTemplate; - existingInfo.subjectLine = newInfo.subjectLine; - existingInfo.fromEmail = newInfo.fromEmail; - md.updateTable(existingInfo); + md.updateTable(newInfo.overlayOn(existingInfo)); return Response.status(200).build(); } catch (SQLException ex) { log.error("Error when trying to update settings: ", ex); diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/OrgMessageSender.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/OrgMessageSender.java index 6325d937..0207b8e6 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/OrgMessageSender.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/OrgMessageSender.java @@ -75,29 +75,29 @@ public OrgMessageSender(Supplier mdSupplier, SendMessag this.sendMsg = sendMsg; } - public MessageInfo getSettings(UUID serverId) { + public MessageInfo getSettings(UUID serverId, Optional fromEmail) { Optional maybeInfo = Optional.empty(); try (MessageSettingsDatabase md = mdSupplier.get()) { - maybeInfo = md.findMessageInfo(serverId); + if (fromEmail.isPresent()) { + maybeInfo = md.findMessageInfo(serverId, fromEmail.get()); + } else { + maybeInfo = md.findMessageInfo(serverId); + } } catch (SQLException ex) { log.error("Couldn't connect to message settings database", ex); } + var defaultSettings = + new MessageInfo( + serverId, + defaultFrom, + defaultSubject, + defaultResponseTemplate, + defaultConfirmationSubject, + defaultConfirmation); if (maybeInfo.isPresent()) { MessageInfo info = maybeInfo.get(); - if (info.fromEmail == null) { - info.fromEmail = defaultFrom; - } - if (info.subjectLine == null) { - info.subjectLine = defaultSubject; - } - if (info.emailResponseTemplate == null) { - info.emailResponseTemplate = defaultResponseTemplate; - } - if (info.emailConfirmation == null) { - info.emailConfirmation = defaultConfirmation; - } - return info; + return info.overlayOn(defaultSettings); } else { log.warn("Couldn't get message settings, using defaults"); return new MessageInfo( @@ -128,10 +128,15 @@ public boolean sendMessage( String messageUrl, String courtName, String theirCaseTitle) { - MessageInfo msgSettings = getSettings(trans.serverId); + Optional maybeFromEmail = + trans.fromEmail == null || trans.fromEmail.isBlank() + ? Optional.empty() + : Optional.of(trans.fromEmail); + MessageInfo msgSettings = getSettings(trans.serverId, maybeFromEmail); + SendMessage customSendMsg = sendMsg.customSender(msgSettings.sendgridApiKey()); Map templateVars = new HashMap(); - String template = msgSettings.emailResponseTemplate; - String subject = msgSettings.subjectLine; + String template = msgSettings.emailResponseTemplate(); + String subject = msgSettings.subjectLine(); // Set neutral msg and subject first, since we prefer personalized messages over // defaults, even if tone isn't perfect. if (trans.neutralMsgTemplate != null && !trans.neutralMsgTemplate.isBlank()) { @@ -174,7 +179,8 @@ public boolean sendMessage( int result; try { result = - sendMsg.sendEmail(msgSettings.fromEmail, subject, trans.email, template, templateVars); + customSendMsg.sendEmail( + msgSettings.fromEmail(), subject, trans.email, template, templateVars); return (result == 200 || result == 202 || result == 204); } catch (IOException e) { log.error("Error when sending email", e); @@ -197,15 +203,17 @@ public boolean sendConfirmation( String caseType, String caseTitle, String envelopeId) { - MessageInfo msgSettings = getSettings(serverId); + MessageInfo msgSettings = getSettings(serverId, tmpls.checkFromEmail()); + SendMessage customSendMsg = sendMsg.customSender(msgSettings.sendgridApiKey()); + String emailTemplate = tmpls.confirmationTemplate(); if (emailTemplate == null || emailTemplate.isBlank()) { log.warn("given email template was blank ({}), using default", emailTemplate); - emailTemplate = msgSettings.emailConfirmation; + emailTemplate = msgSettings.emailConfirmation(); } String emailSubject = tmpls.confirmationSubject(); if (emailSubject == null || emailSubject.isBlank()) { - emailSubject = msgSettings.subjectLine; + emailSubject = msgSettings.subjectLine(); } String ids = transactionIds.stream().map(t -> t.toString()).collect(Collectors.joining(", ")); Map templateVars = new HashMap(); @@ -220,8 +228,8 @@ public boolean sendConfirmation( int result; try { result = - sendMsg.sendEmail( - msgSettings.fromEmail, emailSubject, email, emailTemplate, templateVars); + customSendMsg.sendEmail( + msgSettings.fromEmail(), emailSubject, email, emailTemplate, templateVars); return (result == 200 || result == 202 || result == 204); } catch (IOException e) { log.error("Error when sending confirmation", e); diff --git a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/SendMessage.java b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/SendMessage.java index 614d5d2e..49bc948e 100644 --- a/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/SendMessage.java +++ b/proxyserver/src/main/java/edu/suffolk/litlab/efsp/server/utils/SendMessage.java @@ -14,6 +14,7 @@ import com.twilio.rest.api.v2010.account.Message; import com.twilio.type.PhoneNumber; import edu.suffolk.litlab.efsp.server.setup.EfmModuleSetup; +import edu.suffolk.litlab.efsp.stdlib.SensitiveInfo; import jakarta.mail.Message.RecipientType; import jakarta.mail.MessagingException; import jakarta.mail.PasswordAuthentication; @@ -71,12 +72,12 @@ public static Optional create() { String sendingMethod = maybeSendingMethod.get(); String sendgridApiKey = null; if (sendingMethod.equalsIgnoreCase("sendgrid")) { - Optional maybeKey = GetEnv("SENDGRID_API_KEY"); - if (maybeKey.isEmpty()) { + var maybeApiKey = GetEnv("SENDGRID_API_KEY"); + if (maybeApiKey.isEmpty()) { log.warn("Need SENDGRID_API_KEY if sending method is {}", sendingMethod); return Optional.empty(); } - sendgridApiKey = maybeKey.get(); + sendgridApiKey = maybeApiKey.get(); } List smtpVars; @@ -113,6 +114,26 @@ public static Optional create() { return Optional.of(new SendMessage(sendingMethod, smtpVars, sendgridApiKey, twilioVars)); } + public SendMessage customSender(Optional> maybeApiKey) { + if (!sendingMethod.equalsIgnoreCase("sendgrid") || maybeApiKey.isEmpty()) { + log.info("Using default Sendgrid API key, no server key provided"); + return this; + } + + log.info("Using custom Sendgrid API key for server instead of default"); + return new SendMessage( + sendingMethod, + smtpServer, + smtpPort, + smtpEnableAuth, + smtpUser, + smtpPassword, + maybeApiKey.get().field(), + twilioAccountSid, + twilioAuthToken, + twilioSendingNumber); + } + private SendMessage( String sendingMethod, List smtpVars, String sendgridApiKey, List twilioVars) { this( @@ -191,7 +212,7 @@ public int sendEmail( * @param context A HashMap containing variables that will be substituted in the message_template * @throws IOException If there's a network error */ - public boolean sendSmtpEmail( + private boolean sendSmtpEmail( String from, String subject, String to, String messageTemplate, Map context) throws IOException { @@ -241,7 +262,7 @@ protected PasswordAuthentication getPasswordAuthentication() { * @param context A HashMap containing variables that will be substituted in the message_template * @throws IOException If there's a network error talking to sendgrid */ - public int sendSendgridEmail( + private int sendSendgridEmail( String from, String subject, String to, String messageTemplate, Map context) throws IOException { diff --git a/proxyserver/src/test/java/edu/suffolk/litlab/efsp/db/model/MessageInfoTest.java b/proxyserver/src/test/java/edu/suffolk/litlab/efsp/db/model/MessageInfoTest.java new file mode 100644 index 00000000..d25f4156 --- /dev/null +++ b/proxyserver/src/test/java/edu/suffolk/litlab/efsp/db/model/MessageInfoTest.java @@ -0,0 +1,35 @@ +package edu.suffolk.litlab.efsp.db.model; + +import static org.assertj.core.api.Assertions.assertThat; + +import edu.suffolk.litlab.efsp.stdlib.SensitiveInfo; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.Test; + +public class MessageInfoTest { + @Test + public void testNullStuff() { + var m1 = new MessageInfo(UUID.randomUUID(), null, (String) null, "Subject", null, null, null); + + assertThat(m1.sendgridApiKey()).isEmpty(); + } + + @Test + public void testOverlay() { + var m1 = + new MessageInfo(UUID.randomUUID(), null, Optional.empty(), "Subject", null, null, null); + var m2 = new MessageInfo(UUID.randomUUID(), "m2@example.com", "abc123", null, null, null, null); + + var overlay = m2.overlayOn(m1); + assertThat(overlay.subjectLine()).isEqualTo("Subject"); + assertThat(overlay.fromEmail()).isEqualTo("m2@example.com"); + assertThat(overlay.sendgridApiKey()).isPresent().contains(new SensitiveInfo<>("abc123")); + } + + @Test + public void testSensitivePrinting() { + var m1 = new MessageInfo(UUID.randomUUID(), null, "abc123", null, null, null, null); + assertThat(m1.toString()).doesNotContain("abc123"); + } +}