From 017f16fdf1899ff095a78416c11a8b5781e9d9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenzo=20Dellac=C3=A0?= Date: Sat, 22 Aug 2020 18:32:46 +0200 Subject: [PATCH] Add timestamp support A new "timestamp" entry for the JSON POST request (/api/v1/store) was added. The timestamp must be formatted following ISO_OFFSET_DATE_TIME, eg: YYYY-MM-DDTHH:mm:ss.SSSZ. The storage request will be denied in case the timestamp is missing, or if it's more than 5 minutes old (or more than 1 minute into the future). --- README.MD | 1 + .../net/mindoverflow/webmarker/WebMarker.java | 7 +++ .../webmarker/utils/FileUtils.java | 12 ++++- .../webmarker/utils/sql/MDatabaseColumn.java | 3 +- .../webmarker/utils/sql/MDatabaseTable.java | 2 + .../webmarker/utils/sql/MarkerSQLUtils.java | 14 +++-- .../controllers/StorageController.java | 53 +++++++++++++++++-- 7 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 README.MD diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..07964fe --- /dev/null +++ b/README.MD @@ -0,0 +1 @@ +# webMarker Server diff --git a/src/main/java/net/mindoverflow/webmarker/WebMarker.java b/src/main/java/net/mindoverflow/webmarker/WebMarker.java index e1985cb..8893a10 100644 --- a/src/main/java/net/mindoverflow/webmarker/WebMarker.java +++ b/src/main/java/net/mindoverflow/webmarker/WebMarker.java @@ -8,6 +8,9 @@ import net.mindoverflow.webmarker.utils.sql.SQLiteManager; import net.mindoverflow.webmarker.webserver.WebApplication; import ro.pippo.core.Pippo; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + public class WebMarker { private static final Messenger msg = new Messenger(); @@ -28,6 +31,10 @@ public class WebMarker { pippo.start(port); msg.info("Started webserver."); + ZonedDateTime now = ZonedDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + String nowStr = formatter.format(now); + msg.info("Startup timestamp: " + nowStr); /* todo: enable to track ram usage ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); exec.scheduleAtFixedRate(new StatsRunnable(), 0, 5, TimeUnit.SECONDS); diff --git a/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java b/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java index 76f25c4..5393542 100644 --- a/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java +++ b/src/main/java/net/mindoverflow/webmarker/utils/FileUtils.java @@ -8,7 +8,15 @@ public class FileUtils { public static JsonObject stringToJson(String body) { - JsonParser jsonParser = new JsonParser(); - return (JsonObject) jsonParser.parse(body); + try { + JsonParser jsonParser = new JsonParser(); + return (JsonObject) jsonParser.parse(body); + } catch (Exception e) + { + // todo: better exception (MalformedJsonException) + return null; + } } + + // todo: move more conversion stuff to this class, maybe rename it too? } diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java index a5780cf..4989a79 100644 --- a/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java +++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseColumn.java @@ -10,7 +10,8 @@ public enum MDatabaseColumn USERNAME(new SQLColumn("username"), SQLDataType.VARCHAR_128), PASSWORD(new SQLColumn("password"), SQLDataType.VARCHAR_128), USER_UUID(new SQLColumn("userid"), SQLDataType.VARCHAR_128), - WEB_DOMAIN(new SQLColumn("domain"), SQLDataType.VARCHAR_128), + WEB_DOMAIN(new SQLColumn("domain"), SQLDataType.TEXT), + TIMESTAMP_UTC(new SQLColumn("timestamp_utc"), SQLDataType.DATETIME), ; diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java index 3b3b85e..6ffe456 100644 --- a/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java +++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/MDatabaseTable.java @@ -18,6 +18,7 @@ public enum MDatabaseTable new ArrayList<>(){{ add(MDatabaseColumn.USER_UUID); add(MDatabaseColumn.WEB_DOMAIN); + add(MDatabaseColumn.TIMESTAMP_UTC); }})) @@ -31,3 +32,4 @@ public enum MDatabaseTable public SQLTable getTable() { return table; } } + diff --git a/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java b/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java index 0e7b6bb..a0aa6e8 100644 --- a/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java +++ b/src/main/java/net/mindoverflow/webmarker/utils/sql/MarkerSQLUtils.java @@ -2,6 +2,8 @@ package net.mindoverflow.webmarker.utils.sql; import net.mindoverflow.webmarker.utils.Cached; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; @@ -69,13 +71,19 @@ public class MarkerSQLUtils { return result.get(0); } - public static boolean addHistoryRecord(UUID uuid, String url) + public static boolean addHistoryRecord(UUID uuid, String url, LocalDateTime timestampUTC) { String query = "INSERT INTO " + MDatabaseTable.HISTORY.getTable().getTableSQLName() + " (" + MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() + ", " + - MDatabaseColumn.WEB_DOMAIN.getColumn().getColumnSQLName() + ") VALUES (?, ?);"; + MDatabaseColumn.WEB_DOMAIN.getColumn().getColumnSQLName() + ", " + + MDatabaseColumn.TIMESTAMP_UTC.getColumn().getColumnSQLName() + ") VALUES (?, ?, ?);"; - return Cached.sqlManager.executeUpdate(query, uuid.toString(), url); + DateTimeFormatter sqlDateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + String timestampSQL = sqlDateTime.format(timestampUTC); + + System.out.println("SQL Timestamp: " + timestampSQL); + + return Cached.sqlManager.executeUpdate(query, uuid.toString(), url, timestampSQL); } diff --git a/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java index 7f48c4b..735ade6 100644 --- a/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java +++ b/src/main/java/net/mindoverflow/webmarker/webserver/controllers/StorageController.java @@ -21,6 +21,9 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.UUID; @Path("/api/v1/store") @@ -52,7 +55,7 @@ public class StorageController extends Controller if(username == null) return; // Check some stuff about the user - if(!userCheckPassed(username)) return; + if(!usernameCheckPassed(username)) return; // Load UUID from username UUID uuid = MarkerSQLUtils.getUserUUID(username); @@ -67,14 +70,46 @@ public class StorageController extends Controller if(url == null) return; // Verify url validity - URI confirmedUrl = verifyUrl(url); + URI confirmedUrl = stringToURI(url); if(confirmedUrl == null) return; - MarkerSQLUtils.addHistoryRecord(uuid, confirmedUrl.toString()); + // Load ZonedDateTime from Json body + String zonedDateTimeStr = getFromJson(jsonObject, "timestamp"); + if(zonedDateTimeStr == null) return; + + // Transform String to ZonedDateTime + ZonedDateTime timestamp = stringToZDT(zonedDateTimeStr); + if(timestamp == null) return; + System.out.println("Zoned Timestamp: " + timestamp.toString()); + + // Check if the timestamp is too old to be true + ZonedDateTime now = ZonedDateTime.now(); + System.out.println("NOW Timestamp: " + now.toString()); + if(!dateCheckPassed(timestamp, now)) return; + + // Transform whatever GMT it's on to GMT+0 + LocalDateTime timestampUTC = LocalDateTime.ofInstant(timestamp.toInstant(), ZoneOffset.UTC); + System.out.println("UTC Timestamp: " + timestampUTC.toString()); + + MarkerSQLUtils.addHistoryRecord(uuid, confirmedUrl.toString(), timestampUTC); routeContext.send("OK!"); } + // todo: move to safety class? + private boolean dateCheckPassed(ZonedDateTime timestamp, ZonedDateTime now) + { + // allow up to 5 minutes in the past and 1 minute in the future + //if(ChronoUnit.MINUTES.between(timestamp, now) > 5) todo: decide + if(Duration.between(timestamp, now).getSeconds() >= 5 * 60 + || Duration.between(timestamp, now).getSeconds() <= -1 * 60) + { + routeContext.send("Timestamp out of sync!"); + return false; + } + return true; + } + private JsonElement getJwtFromJson(JsonObject jsonObject) { JsonElement jwtElement = jsonObject.get("jwt"); @@ -116,7 +151,8 @@ public class StorageController extends Controller return claim.asString(); } - private boolean userCheckPassed(String username) + // todo: move to safety class? + private boolean usernameCheckPassed(String username) { if(!SafetyCheck.isSafeUsername(username)) { @@ -146,7 +182,7 @@ public class StorageController extends Controller return jsonElement.getAsString(); } - private URI verifyUrl(String url) + private URI stringToURI(String url) { try { return new URL(url).toURI(); @@ -159,4 +195,11 @@ public class StorageController extends Controller return null; } + + private ZonedDateTime stringToZDT(String timestamp) + { + DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME; + return ZonedDateTime.parse(timestamp, formatter); + // todo: error check + } }