Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bxc 4362 invalidate schedule #1656

Merged
merged 2 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading