Skip to content

Commit

Permalink
Use Instant insteal of LocalDate
Browse files Browse the repository at this point in the history
  • Loading branch information
barreiro committed Sep 27, 2024
1 parent 1ad43ba commit 66ec5da
Show file tree
Hide file tree
Showing 13 changed files with 53 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.hyperfoil.tools.horreum.api.internal.services;

import java.time.LocalDate;
import java.time.Instant;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -195,8 +195,8 @@ class ApiKeyResponse {
public long id;
public String name;
public KeyType type;
public LocalDate creation;
public LocalDate access;
public Instant creation;
public Instant access;
public boolean isRevoked;
public long toExpiration;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Comparator;
Expand Down Expand Up @@ -33,9 +33,15 @@
@Table(name = "userinfo_apikey")
@NamedQueries({
// fetch all keys that expire on a given day
@NamedQuery(name = "UserApiKey.expire", query = "from UserApiKey where not revoked AND (access is null and (creation + active day) = ?1 or (access + active day) = ?1)"),
@NamedQuery(name = "UserApiKey.expire", query = """
from UserApiKey where not revoked and (access is null and trunc(creation + (active day), day) = trunc(cast(?1 as localdatetime), day)
or trunc(access + (active day), day) = trunc(cast(?1 as localdatetime), day))
"""),
// fetch all keys that have gone past their expiration date
@NamedQuery(name = "UserApiKey.pastExpiration", query = "from UserApiKey where not revoked AND (access is null and (creation + active day) < ?1 or (access + active day) < ?1)"),
@NamedQuery(name = "UserApiKey.pastExpiration", query = """
from UserApiKey where not revoked and (access is null and trunc(creation + (active day), day) < trunc(cast(?1 as localdatetime), day)
or trunc(access + (active day), day) < trunc(cast(?1 as localdatetime), day))
"""),
})
public class UserApiKey extends PanacheEntityBase implements Comparable<UserApiKey> {

Expand All @@ -61,7 +67,7 @@ public class UserApiKey extends PanacheEntityBase implements Comparable<UserApiK
@Enumerated
public final UserService.KeyType type;

public LocalDate creation, access;
public Instant creation, access;

public long active; // number of days after last access that the key remains active

Expand All @@ -74,7 +80,7 @@ public UserApiKey() {
type = UserService.KeyType.USER;
}

public UserApiKey(String name, UserService.KeyType type, LocalDate creationDate, long valid) {
public UserApiKey(String name, UserService.KeyType type, Instant creationDate, long valid) {
randomnessSource = UUID.randomUUID();
this.name = name;
this.type = type;
Expand All @@ -84,12 +90,12 @@ public UserApiKey(String name, UserService.KeyType type, LocalDate creationDate,
revoked = false;
}

public boolean isArchived(LocalDate givenDay) {
return givenDay.isAfter((access == null ? creation : access).plusDays(active + ARCHIVE_AFTER_DAYS));
public boolean isArchived(Instant givenDay) {
return givenDay.isAfter((access == null ? creation : access).plus(active + ARCHIVE_AFTER_DAYS, ChronoUnit.DAYS));
}

// calculate the number of days left until expiration (if negative it's the number of days after expiration)
public long toExpiration(LocalDate givenDay) {
public long toExpiration(Instant givenDay) {
return active - ChronoUnit.DAYS.between(access == null ? creation : access, givenDay);
}

Expand Down Expand Up @@ -134,6 +140,6 @@ public int hashCode() {

@Override
public int compareTo(UserApiKey other) {
return Comparator.<UserApiKey, LocalDate> comparing(a -> a.creation).thenComparing(a -> a.id).compare(this, other);
return Comparator.<UserApiKey, Instant> comparing(a -> a.creation).thenComparing(a -> a.id).compare(this, other);
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.hyperfoil.tools.horreum.mapper;

import java.time.LocalDate;
import java.time.Instant;

import io.hyperfoil.tools.horreum.api.internal.services.UserService;
import io.hyperfoil.tools.horreum.entity.user.UserApiKey;

public class UserApiKeyMapper {

public static UserApiKey from(UserService.ApiKeyRequest request, LocalDate creation, long valid) {
public static UserApiKey from(UserService.ApiKeyRequest request, Instant creation, long valid) {
return new UserApiKey(request.name == null ? "" : request.name, request.type, creation, valid);
}

Expand All @@ -19,7 +19,7 @@ public static UserService.ApiKeyResponse to(UserApiKey key) {
response.creation = key.creation;
response.access = key.access;
response.isRevoked = key.revoked;
response.toExpiration = key.toExpiration(LocalDate.now());
response.toExpiration = key.toExpiration(Instant.now());
return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Date;

import jakarta.enterprise.context.ApplicationScoped;
Expand Down Expand Up @@ -156,16 +156,16 @@ public void notifyExpectedRun(String testName, int testId, long before, String e
}

@Override
public void notifyApiKeyExpiration(String keyName, LocalDate creation, LocalDate lastAccess, long toExpiration,
public void notifyApiKeyExpiration(String keyName, Instant creation, Instant lastAccess, long toExpiration,
long active) {
String subject = String.format("%s API key \"%s\" %s", subjectPrefix, keyName,
toExpiration == -1 ? "EXPIRED" : "about to expire");
String content = apiKeyExpirationEmail
.data("baseUrl", baseUrl)
.data("username", username)
.data("keyName", keyName)
.data("creation", creation)
.data("lastAccess", lastAccess)
.data("creation", creation.truncatedTo(ChronoUnit.DAYS))
.data("lastAccess", lastAccess.truncatedTo(ChronoUnit.DAYS))
.data("expiration", toExpiration)
.data("active", active)
.render();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.hyperfoil.tools.horreum.notification;

import java.time.Instant;
import java.time.LocalDate;

import io.hyperfoil.tools.horreum.events.DatasetChanges;
import io.hyperfoil.tools.horreum.svc.MissingValuesEvent;
Expand All @@ -24,7 +23,7 @@ public abstract void notifyMissingDataset(String testName, int testId, String ru

public abstract void notifyExpectedRun(String testName, int testId, long before, String expectedBy, String backlink);

public abstract void notifyApiKeyExpiration(String keyName, LocalDate creation, LocalDate lastAccess, long toExpiration,
public abstract void notifyApiKeyExpiration(String keyName, Instant creation, Instant lastAccess, long toExpiration,
long active);

}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package io.hyperfoil.tools.horreum.server;

import io.quarkus.logging.Log;
import io.quarkus.security.AuthenticationFailedException;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;

import io.hyperfoil.tools.horreum.entity.user.UserApiKey;
import io.hyperfoil.tools.horreum.svc.TimeService;
import io.quarkus.logging.Log;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
Expand Down Expand Up @@ -43,7 +42,7 @@ SecurityIdentity identityFromKey(String key) {
Log.debugv("Authentication of user {0} with key \"{1}\" {2}", userKey.user.username, userKey.name, key);

// update last access
userKey.access = timeService.today();
userKey.access = timeService.now();

// create identity with just the principal, roles will be populated in RolesAugmentor
return QuarkusSecurityIdentity.builder().setPrincipal(new QuarkusPrincipal(userKey.user.username)).build();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.hyperfoil.tools.horreum.svc;

import java.time.Instant;
import java.time.LocalDate;

import jakarta.enterprise.context.ApplicationScoped;

Expand All @@ -13,8 +12,4 @@ public class TimeService {
public Instant now() {
return Instant.now();
}

public LocalDate today() {
return LocalDate.now();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import static java.util.Collections.emptyList;

import java.security.SecureRandom;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import io.hyperfoil.tools.horreum.mapper.UserApiKeyMapper;
import jakarta.annotation.security.PermitAll;
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
Expand All @@ -20,6 +20,7 @@
import io.hyperfoil.tools.horreum.api.internal.services.UserService;
import io.hyperfoil.tools.horreum.entity.user.UserApiKey;
import io.hyperfoil.tools.horreum.entity.user.UserInfo;
import io.hyperfoil.tools.horreum.mapper.UserApiKeyMapper;
import io.hyperfoil.tools.horreum.server.WithRoles;
import io.hyperfoil.tools.horreum.svc.user.UserBackEnd;
import io.quarkus.logging.Log;
Expand Down Expand Up @@ -300,7 +301,7 @@ public String newApiKey(ApiKeyRequest request) {
validateApiKeyName(request.name == null ? "" : request.name);
UserInfo userInfo = currentUser();

UserApiKey newKey = UserApiKeyMapper.from(request, timeService.today(), DEFAULT_API_KEY_ACTIVE_DAYS);
UserApiKey newKey = UserApiKeyMapper.from(request, timeService.now(), DEFAULT_API_KEY_ACTIVE_DAYS);
newKey.user = userInfo;
userInfo.apiKeys.add(newKey);
newKey.persist();
Expand All @@ -315,7 +316,7 @@ public String newApiKey(ApiKeyRequest request) {
@Override
public List<ApiKeyResponse> apiKeys() {
return currentUser().apiKeys.stream()
.filter(t -> !t.isArchived(timeService.today()))
.filter(t -> !t.isArchived(timeService.now()))
.sorted()
.map(UserApiKeyMapper::to)
.toList();
Expand Down Expand Up @@ -353,11 +354,11 @@ public void revokeApiKey(long keyId) {
public void apiKeyDailyTask() {
// notifications of keys expired and about to expire -- hardcoded to send multiple notices in the week prior to expiration
for (long toExpiration : List.of(7, 2, 1, 0, -1)) {
UserApiKey.<UserApiKey> stream("#UserApiKey.expire", timeService.today().plusDays(toExpiration))
UserApiKey.<UserApiKey> stream("#UserApiKey.expire", timeService.now().plus(toExpiration, ChronoUnit.DAYS))
.forEach(key -> notificationServiceimpl.notifyApiKeyExpiration(key, toExpiration));
}
// revoke expired keys -- could be done directly in the DB but iterate instead to be able to log
UserApiKey.<UserApiKey> stream("#UserApiKey.pastExpiration", timeService.today()).forEach(key -> {
UserApiKey.<UserApiKey> stream("#UserApiKey.pastExpiration", timeService.now()).forEach(key -> {
Log.debugv("Idle API key \"{0}\" revoked", key.name);
key.revoked = true;
});
Expand Down
4 changes: 2 additions & 2 deletions horreum-backend/src/main/resources/db/changeLog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4580,8 +4580,8 @@
<column name="name" type="text"/>
<column name="type" type="smallint"/>
<column name="revoked" type="bool"/>
<column name="creation" type="date"/>
<column name="access" type="date"/>
<column name="creation" type="timestamptz"/>
<column name="access" type="timestamptz"/>
<column name="active" type="bigint"/>
</createTable>
<createIndex tableName="userinfo_apikey" indexName="userinfo_apikey_hash">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<p>Hello {username},</p>
<p>The API key "{keyName}" created on {creation}
<p>The API key "{keyName}" created on {creation.format('d MMM uuuu')}
{#when expiration}
{#is < 0} HAS EXPIRED.
{#is < 1} expires today.
{#is < 2} expires tomorrow.
{#else} expires in {expiration} days.
{/when}
</p>
<p>The key was {#if lastAccess == null}never used{#else}last used on {lastAccess}{/if}.</p>
<p>The key was {#if lastAccess == null}never used{#else}last used on {lastAccess.format('d MMM uuuu')}{/if}.</p>
<p>Remember that this key is automatically revoked if not used for {active} days.</p>
<a href="{baseUrl}">Horreum Alerting</a>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.time.LocalDate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
Expand Down Expand Up @@ -612,22 +613,23 @@ void apiKeys() {
assertTrue(
UserApiKey
.<UserApiKey> stream("#UserApiKey.expire",
timeService.today().plusDays(DEFAULT_API_KEY_ACTIVE_DAYS))
timeService.now().plus(DEFAULT_API_KEY_ACTIVE_DAYS, ChronoUnit.DAYS))
.anyMatch(k -> k.id == keys.get(0).id));

// about to expire
assertTrue(notifications.isEmpty());
setApiKeyCreation(keys.get(0).id, timeService.today().minusDays(DEFAULT_API_KEY_ACTIVE_DAYS));
setApiKeyCreation(keys.get(0).id, timeService.now().minus(DEFAULT_API_KEY_ACTIVE_DAYS, ChronoUnit.DAYS));
assertFalse(notifications.isEmpty(), "Expected a notification of key about to expire");
assertEquals(keys.get(0).id, notifications.get(0).id, "Got notification for the wrong key");

// should not be revoked yet
assertFalse(userService.apiKeys().get(0).isRevoked);
assertTrue(UserApiKey.<UserApiKey> stream("#UserApiKey.expire", timeService.today())
assertTrue(UserApiKey.<UserApiKey> stream("#UserApiKey.expire", timeService.now())
.anyMatch(k -> k.id == keys.get(0).id));

// expire it
setApiKeyCreation(keys.get(0).id, timeService.today().minusDays(DEFAULT_API_KEY_ACTIVE_DAYS + 1));
setApiKeyCreation(keys.get(0).id,
timeService.now().minus(DEFAULT_API_KEY_ACTIVE_DAYS + 1, ChronoUnit.DAYS));
assertEquals(2, notifications.toArray().length, "Expected a second notification after key expiration");

// should be revoked now
Expand All @@ -638,7 +640,7 @@ void apiKeys() {
}

@Transactional
void setApiKeyCreation(long keyId, LocalDate creation) {
void setApiKeyCreation(long keyId, Instant creation) {
UserApiKey apiKey = UserApiKey.findById(keyId);
apiKey.access = null;
apiKey.creation = creation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

public class CustomResteasyJackson2Provider extends ResteasyJackson2Provider {
public CustomResteasyJackson2Provider() {
ObjectMapper customJsonMapper = new ObjectMapper();
// This is useful if the client is old and we have added some new properties
customJsonMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// Quarkus uses ISO-8601 for time format https://quarkus.io/guides/rest-json#jackson
customJsonMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
customJsonMapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
customJsonMapper.registerModule(new JavaTimeModule());
this.setMapper(customJsonMapper);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.Instant;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
Expand Down Expand Up @@ -86,8 +86,8 @@ public void testApiKeys() {
UserService.ApiKeyResponse apiKey = horreumClient.userService.apiKeys().get(0);
assertFalse(apiKey.isRevoked);
assertFalse(apiKey.toExpiration < 0);
assertEquals(LocalDate.now(), apiKey.creation);
assertEquals(LocalDate.now(), apiKey.access);
assertEquals(Instant.now().truncatedTo(ChronoUnit.DAYS), apiKey.creation.truncatedTo(ChronoUnit.DAYS));
assertEquals(Instant.now().truncatedTo(ChronoUnit.DAYS), apiKey.access.truncatedTo(ChronoUnit.DAYS));
assertEquals(USER, apiKey.type);

horreumClient.userService.revokeApiKey(apiKey.id);
Expand Down

0 comments on commit 66ec5da

Please sign in to comment.