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).
This commit is contained in:
Bea 2020-08-22 18:32:46 +02:00
parent 07ec036e4f
commit 017f16fdf1
7 changed files with 81 additions and 11 deletions

1
README.MD Normal file
View File

@ -0,0 +1 @@
# webMarker Server

View File

@ -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);

View File

@ -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?
}

View File

@ -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),
;

View File

@ -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; }
}

View File

@ -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);
}

View File

@ -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
}
}