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 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);
|
||||
|
@ -8,7 +8,15 @@ public class FileUtils {
|
||||
|
||||
public static JsonObject stringToJson(String 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?
|
||||
}
|
||||
|
@ -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),
|
||||
|
||||
|
||||
;
|
||||
|
@ -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; }
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user