Skip to content

Commit

Permalink
Bxc 4362 invalidate schedule (#1656)
Browse files Browse the repository at this point in the history
* BXC-4362 add time-based invalidation code and tests

* BXC-4362 update logic to be clearer and add another time based test

---------

Co-authored-by: Sharon Luong <[email protected]>
  • Loading branch information
sharonluong and Sharon Luong authored Jan 23, 2024
1 parent 71e9d63 commit 62f5274
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import edu.unc.lib.boxc.model.api.exceptions.RepositoryException;
import org.apache.commons.csv.CSVRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import java.io.IOException;
import java.nio.file.Path;
Expand All @@ -19,6 +24,8 @@
* Generate and invalidate access keys for single use links
* @author snluong
*/
@Configuration
@EnableScheduling
public class SingleUseKeyService {
public static final String ID = "UUID";
public static final String ACCESS_KEY = "Access Key";
Expand All @@ -28,6 +35,7 @@ public class SingleUseKeyService {
public static final String KEY = "key";
private Path csvPath;
private ReentrantLock lock = new ReentrantLock();
private static final Logger log = LoggerFactory.getLogger(SingleUseKeyService.class);

/**
* Generates an access key for a particular ID, adds it to the CSV, and returns the key
Expand Down Expand Up @@ -70,37 +78,67 @@ public boolean keyIsValid(String key) {
}

/**
* Invalidates a key by removing its entry from the CSV
* @param key access key of the box-c record
* Invalidates a single use key CSV record by removing it from the CSV
* @param key access key of the box-c record, may be null for time-based invalidation
*/
public void invalidate(String key) {
lock.lock();
try {
var csvRecords = parseCsv(CSV_HEADERS, csvPath);
var updatedRecords = new ArrayList<>();
var keyExists = false;
var recordsChanged = false;
for (CSVRecord record : csvRecords) {
if (key.equals(record.get(ACCESS_KEY))) {
keyExists = true;
if (recordShouldBeInvalidated(key, record)) {
recordsChanged = true;
} else {
// add the rest of the keys to list
// keep this record as it is valid
updatedRecords.add(record);
}
}

if (keyExists) {
try (var csvPrinter = createNewCsvPrinter(CSV_HEADERS, csvPath)) {
csvPrinter.flush();
csvPrinter.printRecords(updatedRecords);
}
if (recordsChanged) {
writeNewCsv(updatedRecords);
}
} catch (IOException e) {
throw new RepositoryException("Failed to invalidate key in Single Use Key CSV", e);
throw new RepositoryException("Failed to invalidate record in Single Use Key CSV", e);
} finally {
lock.unlock();
}
}

/**
* Determines if the specific record should be invalidated
* @param key the access key, may be null if we want a time-based eval
* @param row the csv row that is the CSV record
* @return true if the record should be invalidated
*/
private boolean recordShouldBeInvalidated(String key, CSVRecord row) {
if (key == null) {
// if record's timestamp is in the past, it should be invalidated
var currentTime = System.currentTimeMillis();
return Long.parseLong(row.get(TIMESTAMP)) < currentTime;
} else {
// if the record's key matches, it should be invalidated
return key.equals(row.get(ACCESS_KEY));
}
}

/**
* We are running a cron job every hour to invalidate any expired single use keys
*/
@Scheduled(cron = "0 0 * * * *")
public void scheduleInvalidation() {
log.info("Invalidate Single Use Key Cron is running!");
invalidate(null);
}

private void writeNewCsv(ArrayList<Object> records) throws IOException {
try (var csvPrinter = createNewCsvPrinter(CSV_HEADERS, csvPath)) {
csvPrinter.flush();
csvPrinter.printRecords(records);
}
}

public static String getKey() {
return UUID.randomUUID().toString().replace("-", "") + Long.toHexString(System.nanoTime());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public void testKeyIsNotValidCurrentTimeIsMoreThan24hLater() throws IOException
}

@Test
public void testInvalidate() throws IOException {
public void testInvalidateWithKey() throws IOException {
var key = SingleUseKeyService.getKey();
var expirationTimestamp = System.currentTimeMillis() + DAY_MILLISECONDS;
generateDefaultCsv(csvPath, key, expirationTimestamp);
Expand All @@ -124,6 +124,26 @@ public void testInvalidateWhenKeyIsNotPresent() throws IOException {
assertEquals(3, records.size());
}

@Test
public void testInvalidateTimeBasedAllExpired() throws IOException {
var expirationTimestamp = System.currentTimeMillis() - (2 * DAY_MILLISECONDS);
generateDefaultCsv(csvPath,null, expirationTimestamp);
singleUseKeyService.invalidate(null);

var records = parseCsv(CSV_HEADERS, csvPath);
assertEquals(0, records.size());
}

@Test
public void testInvalidateTimeBasedNoneExpired() throws IOException {
var expirationTimestamp = System.currentTimeMillis() + (2 * DAY_MILLISECONDS);
generateDefaultCsv(csvPath,null, expirationTimestamp);
singleUseKeyService.invalidate(null);

var records = parseCsv(CSV_HEADERS, csvPath);
assertEquals(3, records.size());
}

@Test
public void testInvalidateMultipleTimes() throws IOException {
var key = SingleUseKeyService.getKey();
Expand Down

0 comments on commit 62f5274

Please sign in to comment.