-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1644 from UNC-Libraries/BXC-4358-manage-links-ser…
…vice Bxc-4358 manage links service
- Loading branch information
Showing
4 changed files
with
345 additions
and
18 deletions.
There are no files selected for viewing
108 changes: 108 additions & 0 deletions
108
...vices-app/src/main/java/edu/unc/lib/boxc/web/services/processing/SingleUseKeyService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package edu.unc.lib.boxc.web.services.processing; | ||
|
||
import edu.unc.lib.boxc.model.api.exceptions.RepositoryException; | ||
import org.apache.commons.csv.CSVRecord; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Path; | ||
import java.util.ArrayList; | ||
import java.util.UUID; | ||
import java.util.concurrent.locks.ReentrantLock; | ||
|
||
import static edu.unc.lib.boxc.web.services.utils.CsvUtil.createCsvPrinter; | ||
import static edu.unc.lib.boxc.web.services.utils.CsvUtil.createNewCsvPrinter; | ||
import static edu.unc.lib.boxc.web.services.utils.CsvUtil.parseCsv; | ||
|
||
/** | ||
* Generate and invalidate access keys for single use links | ||
* @author snluong | ||
*/ | ||
public class SingleUseKeyService { | ||
public static final String ID = "UUID"; | ||
public static final String ACCESS_KEY = "Access Key"; | ||
public static final String TIMESTAMP = "Expiration Timestamp"; | ||
public static final String[] CSV_HEADERS = new String[] {ID, ACCESS_KEY, TIMESTAMP}; | ||
public static final long DAY_MILLISECONDS = 86400000; | ||
private Path csvPath; | ||
private ReentrantLock lock = new ReentrantLock(); | ||
|
||
/** | ||
* Generates an access key for a particular ID, adds it to the CSV, and returns the key | ||
* @param id UUID of the record | ||
* @return generated access key | ||
*/ | ||
public String generate(String id) { | ||
var key = getKey(); | ||
lock.lock(); | ||
var expirationInMilliseconds = System.currentTimeMillis() + DAY_MILLISECONDS; | ||
try (var csvPrinter = createCsvPrinter(CSV_HEADERS, csvPath)) { | ||
csvPrinter.printRecord(id, key, expirationInMilliseconds); | ||
} catch (Exception e) { | ||
throw new RepositoryException("Failed to write new key to Single Use Key CSV", e); | ||
} finally { | ||
lock.unlock(); | ||
} | ||
return key; | ||
} | ||
|
||
/** | ||
* Determines if a key is valid by seeing if it is in the CSV and if the expiration timestamp has not passed | ||
* @param key access key for single use link | ||
* @return true if key is in the CSV, otherwise false | ||
*/ | ||
public boolean keyIsValid(String key) { | ||
try { | ||
var csvRecords = parseCsv(CSV_HEADERS, csvPath); | ||
var currentMilliseconds = System.currentTimeMillis(); | ||
for (CSVRecord record : csvRecords) { | ||
if (key.equals(record.get(ACCESS_KEY))) { | ||
var expirationTimestamp = Long.parseLong(record.get(TIMESTAMP)); | ||
return currentMilliseconds <= expirationTimestamp; | ||
} | ||
} | ||
} catch (IOException e) { | ||
throw new RepositoryException("Failed to determine if key is valid in Single Use Key CSV", e); | ||
} | ||
return false; | ||
} | ||
|
||
/** | ||
* Invalidates a key by removing its entry from the CSV | ||
* @param key access key of the box-c record | ||
*/ | ||
public void invalidate(String key) { | ||
lock.lock(); | ||
try { | ||
var csvRecords = parseCsv(CSV_HEADERS, csvPath); | ||
var updatedRecords = new ArrayList<>(); | ||
var keyExists = false; | ||
for (CSVRecord record : csvRecords) { | ||
if (key.equals(record.get(ACCESS_KEY))) { | ||
keyExists = true; | ||
} else { | ||
// add the rest of the keys to list | ||
updatedRecords.add(record); | ||
} | ||
} | ||
|
||
if (keyExists) { | ||
try (var csvPrinter = createNewCsvPrinter(CSV_HEADERS, csvPath)) { | ||
csvPrinter.flush(); | ||
csvPrinter.printRecords(updatedRecords); | ||
} | ||
} | ||
} catch (IOException e) { | ||
throw new RepositoryException("Failed to invalidate key in Single Use Key CSV", e); | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
public static String getKey() { | ||
return UUID.randomUUID().toString().replace("-", "") + Long.toHexString(System.nanoTime()); | ||
} | ||
|
||
public void setCsvPath(Path csvPath) { | ||
this.csvPath = csvPath; | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
web-services-app/src/main/java/edu/unc/lib/boxc/web/services/utils/CsvUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package edu.unc.lib.boxc.web.services.utils; | ||
|
||
import org.apache.commons.csv.CSVFormat; | ||
import org.apache.commons.csv.CSVParser; | ||
import org.apache.commons.csv.CSVPrinter; | ||
import org.apache.commons.csv.CSVRecord; | ||
|
||
import java.io.IOException; | ||
import java.io.Reader; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.nio.file.StandardOpenOption; | ||
import java.util.List; | ||
|
||
/** | ||
* Util for CSV-related operations | ||
* @author snluong | ||
*/ | ||
public class CsvUtil { | ||
public static List<CSVRecord> parseCsv(String[] headers, Path csvPath) throws IOException { | ||
Reader reader = Files.newBufferedReader(csvPath); | ||
return new CSVParser(reader, CSVFormat.DEFAULT | ||
.withFirstRecordAsHeader() | ||
.withHeader(headers) | ||
.withTrim()) | ||
.getRecords(); | ||
} | ||
|
||
/** | ||
* Creates a CSV printer and determines whether new rows should be appended to existing CSV | ||
* @param headers header values of the CSV | ||
* @param csvPath path of the CSV | ||
* @return | ||
* @throws IOException | ||
*/ | ||
public static CSVPrinter createCsvPrinter(String[] headers, Path csvPath) throws IOException { | ||
if (Files.exists(csvPath)) { | ||
var writer = Files.newBufferedWriter(csvPath, StandardOpenOption.APPEND); | ||
return new CSVPrinter(writer, CSVFormat.DEFAULT.withSkipHeaderRecord()); | ||
} else { | ||
return createNewCsvPrinter(headers, csvPath); | ||
} | ||
} | ||
|
||
/** | ||
* Make a new CSV printer that does not append new rows | ||
* @param headers header values of the CSV | ||
* @param csvPath path of the CSV | ||
* @return | ||
* @throws IOException | ||
*/ | ||
public static CSVPrinter createNewCsvPrinter(String[] headers, Path csvPath) throws IOException { | ||
var writer = Files.newBufferedWriter(csvPath); | ||
return new CSVPrinter(writer, CSVFormat.DEFAULT | ||
.withHeader(headers)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.