Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query and Filter Any Event #150

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ngafid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Setup JDK 17
- name: Setup JDK 21
uses: actions/setup-java@v3
with:
java-version: 17
java-version: 21
distribution: 'adopt'

- name: Cache Maven packages
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ db/db_info.php
db/db_info
email_info.txt
package-lock.json
data/archive/*
23 changes: 18 additions & 5 deletions db/create_tables.php
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,16 @@
);";
query_ngafid_db($query);


$query = "CREATE TABLE `email_unsubscribe_tokens` (
`token` VARCHAR(64) NOT NULL,
`user_id` INT(11) NOT NULL,
`expiration_date` DATETIME NOT NULL,
PRIMARY KEY (`token`),
FOREIGN KEY (`user_id`) REFERENCES user(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
query_ngafid_db($query);


$query = "CREATE TABLE `stored_filters` (
`fleet_id` INT(11) NOT NULL,
Expand Down Expand Up @@ -603,11 +613,13 @@
`api_secret` varchar(64) NOT NULL,
`last_upload_time` timestamp ON UPDATE CURRENT_TIMESTAMP,
`timeout` int(11) DEFAULT NULL,
`mutex` TINYINT DEFAULT 0,
KEY `airsync_fleet_id_fk` (`fleet_id`),
CONSTRAINT `airsync_fleet_id_fk` FOREIGN KEY (`fleet_id`) REFERENCES `fleet` (`id`)
);";
`mutex` TINYINT DEFAULT 0,

PRIMARY KEY(`fleet_id`),
FOREIGN KEY(`fleet_id`) REFERENCES `fleet`(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";

# CONSTRAINT `airsync_fleet_id_fk` FOREIGN KEY (`fleet_id`) REFERENCES `fleet` (`id`)
query_ngafid_db($query);

$query = "CREATE TABLE `airsync_imports` (
Expand Down Expand Up @@ -725,4 +737,5 @@
query_ngafid_db($query);

}
?>

?>
10 changes: 6 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,10 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>16</source>
<target>16</target>

<source>20</source>
<target>20</target>

<compilerArgs>
<arg>-Xlint:all</arg>
<arg>-Xmaxwarns</arg>
Expand All @@ -152,8 +154,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>16</source>
<target>16</target>
<source>20</source>
<target>20</target>
</configuration>
</plugin>
</plugins>
Expand Down
147 changes: 134 additions & 13 deletions src/main/java/org/ngafid/SendEmail.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import javax.mail.internet.*;
import javax.activation.*;


public class SendEmail {

private static String password;
Expand All @@ -33,6 +32,16 @@ public class SendEmail {

private static final Logger LOG = Logger.getLogger(SendEmail.class.getName());

private static final String baseURL = "https://ngafid.org";
//private static final String baseURL = "https://ngafidbeta.rit.edu";
private static final String unsubscribeURLTemplate = (baseURL + "/email_unsubscribe?id=__ID__&token=__TOKEN__");
private static final java.sql.Date lastTokenFree = new java.sql.Date(0);
private static final int EMAIL_UNSUBSCRIBE_TOKEN_EXPIRATION_MONTHS = 3;
private static final int MS_PER_DAY = 86_400_000;
private static final int EXPIRATION_POLL_THRESHOLD_MS = (MS_PER_DAY); //Minimum number of milliseconds needed before trying to free old tokens



static {

String enabled = System.getenv("NGAFID_EMAIL_ENABLED");
Expand Down Expand Up @@ -145,6 +154,75 @@ public boolean isValid() {

}


public static void freeExpiredUnsubscribeTokens() {

Calendar calendar = Calendar.getInstance();
java.sql.Date currentDate = new java.sql.Date(calendar.getTimeInMillis());

//Wait at least 24 hours before trying to free tokens again
long tokenDeltaTime = (currentDate.getTime() - lastTokenFree.getTime());
LOG.info("Token timings: DELTA/CURRENT/LAST: " + tokenDeltaTime + " " + currentDate.getTime() + " " + lastTokenFree.getTime());
if (tokenDeltaTime < EXPIRATION_POLL_THRESHOLD_MS) {
LOG.info("Not attempting to free expired tokens (only " + tokenDeltaTime + " / " + EXPIRATION_POLL_THRESHOLD_MS + " milliseconds have passed)");
return;
}
lastTokenFree.setTime(currentDate.getTime());

try (Connection connection = Database.getConnection()) {

String query = "DELETE FROM email_unsubscribe_tokens WHERE expiration_date < ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setDate(1, currentDate);
preparedStatement.execute();

LOG.info("Freed expired email unsubscribe tokens");

} catch (Exception e) {
LOG.severe("Failed to free expired email unsubscribe tokens");
}

}

private static String generateUnsubscribeToken(String recipientEmail, int userID) {

//Generate a random string
String token = UUID.randomUUID().toString().replace("-", "");

Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, EMAIL_UNSUBSCRIBE_TOKEN_EXPIRATION_MONTHS);
java.sql.Date expirationDate = new java.sql.Date(calendar.getTimeInMillis());

try (Connection connection = Database.getConnection()) {

String query = "INSERT INTO email_unsubscribe_tokens (token, user_id, expiration_date) VALUES (?, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(query);
preparedStatement.setString(1, token);
preparedStatement.setInt(2, userID);
preparedStatement.setDate(3, expirationDate);
preparedStatement.execute();

} catch (Exception e) {
LOG.severe("Failed to generate token for email recipient: "+recipientEmail);
}

//Log the token's expiration date
Calendar expirationCalendar = Calendar.getInstance();
expirationCalendar.setTime(expirationDate);
String expirationDateString =
expirationCalendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.US)
+ " " + expirationCalendar.get(Calendar.DAY_OF_MONTH)
+ ", " + expirationCalendar.get(Calendar.YEAR)
+ " " + expirationCalendar.get(Calendar.HOUR_OF_DAY)
+ ":" + expirationCalendar.get(Calendar.MINUTE)
+ ":" + expirationCalendar.get(Calendar.SECOND);

LOG.info("Generated email unsubscribe token for " + recipientEmail + ": " + token + " (Expires at " + expirationDateString + ")");

return token;

}

/**
* Wrapper for sending an email to NGAFID admins
* @param subject - subject of the email
Expand All @@ -163,6 +241,9 @@ public static void sendEmail(ArrayList<String> toRecipients, ArrayList<String> b
return;
}

//Attempt to free expired tokens
freeExpiredUnsubscribeTokens();

//System.out.println(String.format("Username: %s, PW: %s", username, password));

if (auth.isValid()) {
Expand Down Expand Up @@ -195,6 +276,8 @@ public static void sendEmail(ArrayList<String> toRecipients, ArrayList<String> b

try {

/* SEND TO toRecipients */

// Create a default MimeMessage object.
MimeMessage message = new MimeMessage(session);

Expand All @@ -210,36 +293,74 @@ public static void sendEmail(ArrayList<String> toRecipients, ArrayList<String> b
}

//Check if the emailType is forced
boolean embedUnsubscribeURL = true;
if (EmailType.isForced(emailType)) {

System.out.println("Delivering FORCED email type: " + emailType);
embedUnsubscribeURL = false;

} else if (!UserEmailPreferences.getEmailTypeUserState(toRecipient, emailType)) { //Check whether or not the emailType is enabled for the user

continue;

}

System.out.println("EMAILING TO: " + toRecipient);
String bodyPersonalized = body;
if (embedUnsubscribeURL) {

try {
int userID = UserEmailPreferences.getUserIDFromEmail(toRecipient);

//Generate a token for the user to unsubscribe
String token = generateUnsubscribeToken(toRecipient, userID);
String unsubscribeURL = unsubscribeURLTemplate.replace("__ID__", Integer.toString(userID)).replace("__TOKEN__", token);

message.addRecipient(Message.RecipientType.TO, new InternetAddress(toRecipient));
//Embed the Unsubscribe URL at the top of the email body
bodyPersonalized = ("<a href=\""+unsubscribeURL+"\">Unsubscribe from non-critical NGAFID emails</a><br><br>" + body);

}
catch(Exception e) {
LOG.severe("Recipient email "+toRecipient+" is not mapped in UserEmailPreferences, skipping unsubscribe URL");
}

}

for (String bccRecipient : bccRecipients) {
message.addRecipient(Message.RecipientType.BCC, new InternetAddress(bccRecipient));
// Set Subject: header field
message.setSubject(subject);
message.setContent(bodyPersonalized, "text/html; charset=utf-8");

message.setRecipient(Message.RecipientType.TO, new InternetAddress(toRecipient));

// Send the message to the current recipient
System.out.println("sending message!");
Transport.send(message);

}

/* SEND TO bccRecipients */
if (!bccRecipients.isEmpty()) {

// Create a default MimeMessage object.
message = new MimeMessage(session);

// Set From: header field of the header.
message.setFrom(new InternetAddress(from));

for (String bccRecipient : bccRecipients) {
message.addRecipient(Message.RecipientType.BCC, new InternetAddress(bccRecipient));
}

// Set Subject: header field
message.setSubject(subject);
// Set Subject: header field
message.setSubject(subject);
message.setContent(body, "text/html; charset=utf-8");

// Now set the actual message
message.setContent(body, "text/html; charset=utf-8");
// Send the message to the current BCC recipients
System.out.println("sending message (BCC)!");
Transport.send(message);

}

// Send message
System.out.println("sending message!");
Transport.send(message);
System.out.println("Sent message successfully....");
System.out.println("Sent messages successfully....");

} catch (MessagingException mex) {
mex.printStackTrace();
Expand Down
43 changes: 39 additions & 4 deletions src/main/java/org/ngafid/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@
import java.io.PrintWriter;
import java.io.StringWriter;

import java.io.IOException;

import java.time.*;
import java.io.PrintWriter;
import java.io.StringWriter;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.logging.LogManager;
import java.util.logging.Logger;


import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.*;
import com.google.gson.TypeAdapter;

import static org.ngafid.SendEmail.sendAdminEmails;

Expand All @@ -33,12 +38,33 @@
*/
public final class WebServer {
private static final Logger LOG = Logger.getLogger(WebServer.class.getName());
public static final Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();

public static final String NGAFID_UPLOAD_DIR;
public static final String NGAFID_ARCHIVE_DIR;
public static final String MUSTACHE_TEMPLATE_DIR;

public static class LocalDateTimeTypeAdapter extends TypeAdapter<LocalDateTime> {
@Override
public void write(final JsonWriter jsonWriter, final LocalDateTime localDate) throws IOException {
if (localDate == null) {
jsonWriter.nullValue();
return;
}
jsonWriter.value(localDate.toString());
}

@Override
public LocalDateTime read(final JsonReader jsonReader) throws IOException {
if (jsonReader.peek() == JsonToken.NULL) {
jsonReader.nextNull();
return null;
}
return ZonedDateTime.parse(jsonReader.nextString()).toLocalDateTime();
}
}

public static final Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().registerTypeAdapter(LocalDateTime.class, new LocalDateTimeTypeAdapter()).create();

static {

if (System.getenv("NGAFID_UPLOAD_DIR") == null) {
Expand Down Expand Up @@ -207,6 +233,13 @@ public static void main(String[] args) {
Spark.get("/reset_password", new GetResetPassword(gson));
Spark.post("/reset_password", new PostResetPassword(gson));

//to unsubscribe from emails
Spark.get("/email_unsubscribe", new GetEmailUnsubscribe(gson));
Spark.after("/email_unsubscribe", (request, response) -> {
response.redirect("/");
});


Spark.get("/protected/welcome", new GetWelcome(gson));
Spark.get("/protected/aggregate", new GetAggregate(gson));
Spark.post("/protected/event_counts", new PostEventCounts(gson, false));
Expand All @@ -217,6 +250,7 @@ public static void main(String[] args) {
Spark.post("/protected/monthly_event_counts", new PostMonthlyEventCounts(gson));
Spark.get("/protected/severities", new GetSeverities(gson));
Spark.post("/protected/severities", new PostSeverities(gson));
Spark.post("/protected/all_severities", new PostAllSeverities(gson));

Spark.get("/protected/event_statistics", new GetEventStatistics(gson));
Spark.get("/protected/waiting", new GetWaiting(gson));
Expand Down Expand Up @@ -325,6 +359,7 @@ public static void main(String[] args) {
Spark.post("/protected/preferences_metric", new PostUserPreferencesMetric(gson));
Spark.post("/protected/update_tail", new PostUpdateTail(gson));
Spark.post("/protected/update_email_preferences", new PostUpdateUserEmailPreferences(gson));


// Event Definition Management
Spark.get("/protected/manage_event_definitions", new GetAllEventDefinitions(gson));
Expand Down Expand Up @@ -363,4 +398,4 @@ public static void main(String[] args) {

LOG.info("NGAFID WebServer initialization complete.");
}
}
}
Loading