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:
parent
07ec036e4f
commit
017f16fdf1
@ -8,6 +8,9 @@ import net.mindoverflow.webmarker.utils.sql.SQLiteManager;
|
|||||||
import net.mindoverflow.webmarker.webserver.WebApplication;
|
import net.mindoverflow.webmarker.webserver.WebApplication;
|
||||||
import ro.pippo.core.Pippo;
|
import ro.pippo.core.Pippo;
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
public class WebMarker {
|
public class WebMarker {
|
||||||
|
|
||||||
private static final Messenger msg = new Messenger();
|
private static final Messenger msg = new Messenger();
|
||||||
@ -28,6 +31,10 @@ public class WebMarker {
|
|||||||
pippo.start(port);
|
pippo.start(port);
|
||||||
msg.info("Started webserver.");
|
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
|
/* todo: enable to track ram usage
|
||||||
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
|
ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
|
||||||
exec.scheduleAtFixedRate(new StatsRunnable(), 0, 5, TimeUnit.SECONDS);
|
exec.scheduleAtFixedRate(new StatsRunnable(), 0, 5, TimeUnit.SECONDS);
|
||||||
|
@ -8,7 +8,15 @@ public class FileUtils {
|
|||||||
|
|
||||||
public static JsonObject stringToJson(String body)
|
public static JsonObject stringToJson(String body)
|
||||||
{
|
{
|
||||||
JsonParser jsonParser = new JsonParser();
|
try {
|
||||||
return (JsonObject) jsonParser.parse(body);
|
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?
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,8 @@ public enum MDatabaseColumn
|
|||||||
USERNAME(new SQLColumn("username"), SQLDataType.VARCHAR_128),
|
USERNAME(new SQLColumn("username"), SQLDataType.VARCHAR_128),
|
||||||
PASSWORD(new SQLColumn("password"), SQLDataType.VARCHAR_128),
|
PASSWORD(new SQLColumn("password"), SQLDataType.VARCHAR_128),
|
||||||
USER_UUID(new SQLColumn("userid"), 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),
|
||||||
|
|
||||||
|
|
||||||
;
|
;
|
||||||
|
@ -18,6 +18,7 @@ public enum MDatabaseTable
|
|||||||
new ArrayList<>(){{
|
new ArrayList<>(){{
|
||||||
add(MDatabaseColumn.USER_UUID);
|
add(MDatabaseColumn.USER_UUID);
|
||||||
add(MDatabaseColumn.WEB_DOMAIN);
|
add(MDatabaseColumn.WEB_DOMAIN);
|
||||||
|
add(MDatabaseColumn.TIMESTAMP_UTC);
|
||||||
|
|
||||||
}}))
|
}}))
|
||||||
|
|
||||||
@ -31,3 +32,4 @@ public enum MDatabaseTable
|
|||||||
public SQLTable getTable()
|
public SQLTable getTable()
|
||||||
{ return table; }
|
{ return table; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@ package net.mindoverflow.webmarker.utils.sql;
|
|||||||
|
|
||||||
import net.mindoverflow.webmarker.utils.Cached;
|
import net.mindoverflow.webmarker.utils.Cached;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -69,13 +71,19 @@ public class MarkerSQLUtils {
|
|||||||
return result.get(0);
|
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() + " (" +
|
String query = "INSERT INTO " + MDatabaseTable.HISTORY.getTable().getTableSQLName() + " (" +
|
||||||
MDatabaseColumn.USER_UUID.getColumn().getColumnSQLName() + ", " +
|
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);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,9 @@ import java.net.MalformedURLException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.time.*;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Path("/api/v1/store")
|
@Path("/api/v1/store")
|
||||||
@ -52,7 +55,7 @@ public class StorageController extends Controller
|
|||||||
if(username == null) return;
|
if(username == null) return;
|
||||||
|
|
||||||
// Check some stuff about the user
|
// Check some stuff about the user
|
||||||
if(!userCheckPassed(username)) return;
|
if(!usernameCheckPassed(username)) return;
|
||||||
|
|
||||||
// Load UUID from username
|
// Load UUID from username
|
||||||
UUID uuid = MarkerSQLUtils.getUserUUID(username);
|
UUID uuid = MarkerSQLUtils.getUserUUID(username);
|
||||||
@ -67,14 +70,46 @@ public class StorageController extends Controller
|
|||||||
if(url == null) return;
|
if(url == null) return;
|
||||||
|
|
||||||
// Verify url validity
|
// Verify url validity
|
||||||
URI confirmedUrl = verifyUrl(url);
|
URI confirmedUrl = stringToURI(url);
|
||||||
if(confirmedUrl == null) return;
|
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!");
|
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)
|
private JsonElement getJwtFromJson(JsonObject jsonObject)
|
||||||
{
|
{
|
||||||
JsonElement jwtElement = jsonObject.get("jwt");
|
JsonElement jwtElement = jsonObject.get("jwt");
|
||||||
@ -116,7 +151,8 @@ public class StorageController extends Controller
|
|||||||
return claim.asString();
|
return claim.asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean userCheckPassed(String username)
|
// todo: move to safety class?
|
||||||
|
private boolean usernameCheckPassed(String username)
|
||||||
{
|
{
|
||||||
if(!SafetyCheck.isSafeUsername(username))
|
if(!SafetyCheck.isSafeUsername(username))
|
||||||
{
|
{
|
||||||
@ -146,7 +182,7 @@ public class StorageController extends Controller
|
|||||||
return jsonElement.getAsString();
|
return jsonElement.getAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI verifyUrl(String url)
|
private URI stringToURI(String url)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return new URL(url).toURI();
|
return new URL(url).toURI();
|
||||||
@ -159,4 +195,11 @@ public class StorageController extends Controller
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ZonedDateTime stringToZDT(String timestamp)
|
||||||
|
{
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||||
|
return ZonedDateTime.parse(timestamp, formatter);
|
||||||
|
// todo: error check
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user