Browse Source

"Lists" feature is now generic.

This means that no additional structural code is required to add new lists. The only non-generic aspect are the API endpoints - it's best to keep these specific until we have a need for user-created lists.
pull/63/head
CalDescent 3 years ago
parent
commit
394ced9fb9
  1. 8
      src/main/java/org/qortal/api/model/ListRequest.java
  2. 131
      src/main/java/org/qortal/api/resource/ListsResource.java
  3. 17
      src/main/java/org/qortal/list/ResourceList.java
  4. 91
      src/main/java/org/qortal/list/ResourceListManager.java
  5. 2
      src/main/java/org/qortal/transaction/ChatTransaction.java

8
src/main/java/org/qortal/api/model/AddressListRequest.java → src/main/java/org/qortal/api/model/ListRequest.java

@ -7,12 +7,12 @@ import javax.xml.bind.annotation.XmlAccessorType;
import java.util.List; import java.util.List;
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)
public class AddressListRequest { public class ListRequest {
@Schema(description = "A list of addresses") @Schema(description = "A list of items")
public List<String> addresses; public List<String> items;
public AddressListRequest() { public ListRequest() {
} }
} }

131
src/main/java/org/qortal/api/resource/ListsResource.java

@ -9,7 +9,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.qortal.api.*; import org.qortal.api.*;
import org.qortal.api.model.AddressListRequest; import org.qortal.api.model.ListRequest;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.account.AccountData; import org.qortal.data.account.AccountData;
import org.qortal.list.ResourceListManager; import org.qortal.list.ResourceListManager;
@ -30,38 +30,8 @@ public class ListsResource {
@Context @Context
HttpServletRequest request; HttpServletRequest request;
@POST
@Path("/blacklist/address/{address}")
@Operation(
summary = "Add a QORT address to the local blacklist",
responses = {
@ApiResponse(
description = "Returns true on success, or an exception on failure",
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
)
}
)
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public String addAddressToBlacklist(@PathParam("address") String address) {
Security.checkApiCallAllowed(request);
if (!Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
AccountData accountData = repository.getAccountRepository().getAccount(address);
// Not found?
if (accountData == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
// Valid address, so go ahead and blacklist it
boolean success = ResourceListManager.getInstance().addAddressToBlacklist(address, true);
return success ? "true" : "false"; /* Address blacklist */
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@POST @POST
@Path("/blacklist/addresses") @Path("/blacklist/addresses")
@ -72,7 +42,7 @@ public class ListsResource {
content = @Content( content = @Content(
mediaType = MediaType.APPLICATION_JSON, mediaType = MediaType.APPLICATION_JSON,
schema = @Schema( schema = @Schema(
implementation = AddressListRequest.class implementation = ListRequest.class
) )
) )
), ),
@ -86,10 +56,10 @@ public class ListsResource {
} }
) )
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE}) @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public String addAddressesToBlacklist(AddressListRequest addressListRequest) { public String addAddressesToBlacklist(ListRequest listRequest) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
if (addressListRequest == null || addressListRequest.addresses == null) { if (listRequest == null || listRequest.items == null) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
} }
@ -98,7 +68,7 @@ public class ListsResource {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
for (String address : addressListRequest.addresses) { for (String address : listRequest.items) {
if (!Crypto.isValidAddress(address)) { if (!Crypto.isValidAddress(address)) {
errorCount++; errorCount++;
@ -113,7 +83,7 @@ public class ListsResource {
} }
// Valid address, so go ahead and blacklist it // Valid address, so go ahead and blacklist it
boolean success = ResourceListManager.getInstance().addAddressToBlacklist(address, false); boolean success = ResourceListManager.getInstance().addToList("blacklist", "addresses", address, false);
if (success) { if (success) {
successCount++; successCount++;
} }
@ -127,50 +97,16 @@ public class ListsResource {
if (successCount > 0 && errorCount == 0) { if (successCount > 0 && errorCount == 0) {
// All were successful, so save the blacklist // All were successful, so save the blacklist
ResourceListManager.getInstance().saveBlacklist(); ResourceListManager.getInstance().saveList("blacklist", "addresses");
return "true"; return "true";
} }
else { else {
// Something went wrong, so revert // Something went wrong, so revert
ResourceListManager.getInstance().revertBlacklist(); ResourceListManager.getInstance().revertList("blacklist", "addresses");
return "false"; return "false";
} }
} }
@DELETE
@Path("/blacklist/address/{address}")
@Operation(
summary = "Remove a QORT address from the local blacklist",
responses = {
@ApiResponse(
description = "Returns true on success, or an exception on failure",
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
)
}
)
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public String removeAddressFromBlacklist(@PathParam("address") String address) {
Security.checkApiCallAllowed(request);
if (!Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
AccountData accountData = repository.getAccountRepository().getAccount(address);
// Not found?
if (accountData == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
// Valid address, so go ahead and blacklist it
boolean success = ResourceListManager.getInstance().removeAddressFromBlacklist(address, true);
return success ? "true" : "false";
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
}
@DELETE @DELETE
@Path("/blacklist/addresses") @Path("/blacklist/addresses")
@Operation( @Operation(
@ -180,7 +116,7 @@ public class ListsResource {
content = @Content( content = @Content(
mediaType = MediaType.APPLICATION_JSON, mediaType = MediaType.APPLICATION_JSON,
schema = @Schema( schema = @Schema(
implementation = AddressListRequest.class implementation = ListRequest.class
) )
) )
), ),
@ -194,10 +130,10 @@ public class ListsResource {
} }
) )
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE}) @ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public String removeAddressesFromBlacklist(AddressListRequest addressListRequest) { public String removeAddressesFromBlacklist(ListRequest listRequest) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
if (addressListRequest == null || addressListRequest.addresses == null) { if (listRequest == null || listRequest.items == null) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
} }
@ -206,7 +142,7 @@ public class ListsResource {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
for (String address : addressListRequest.addresses) { for (String address : listRequest.items) {
if (!Crypto.isValidAddress(address)) { if (!Crypto.isValidAddress(address)) {
errorCount++; errorCount++;
@ -222,7 +158,7 @@ public class ListsResource {
// Valid address, so go ahead and blacklist it // Valid address, so go ahead and blacklist it
// Don't save as we will do this at the end of the process // Don't save as we will do this at the end of the process
boolean success = ResourceListManager.getInstance().removeAddressFromBlacklist(address, false); boolean success = ResourceListManager.getInstance().removeFromList("blacklist", "addresses", address, false);
if (success) { if (success) {
successCount++; successCount++;
} }
@ -236,12 +172,12 @@ public class ListsResource {
if (successCount > 0 && errorCount == 0) { if (successCount > 0 && errorCount == 0) {
// All were successful, so save the blacklist // All were successful, so save the blacklist
ResourceListManager.getInstance().saveBlacklist(); ResourceListManager.getInstance().saveList("blacklist", "addresses");
return "true"; return "true";
} }
else { else {
// Something went wrong, so revert // Something went wrong, so revert
ResourceListManager.getInstance().revertBlacklist(); ResourceListManager.getInstance().revertList("blacklist", "addresses");
return "false"; return "false";
} }
} }
@ -259,40 +195,7 @@ public class ListsResource {
) )
public String getAddressBlacklist() { public String getAddressBlacklist() {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
return ResourceListManager.getInstance().getBlacklistJSONString(); return ResourceListManager.getInstance().getJSONStringForList("blacklist", "addresses");
}
@GET
@Path("/blacklist/address/{address}")
@Operation(
summary = "Check if an address is present in the local blacklist",
responses = {
@ApiResponse(
description = "Returns true or false if the list was queried, or an exception on failure",
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "boolean"))
)
}
)
@ApiErrors({ApiError.INVALID_ADDRESS, ApiError.ADDRESS_UNKNOWN, ApiError.REPOSITORY_ISSUE})
public String checkAddressInBlacklist(@PathParam("address") String address) {
Security.checkApiCallAllowed(request);
if (!Crypto.isValidAddress(address))
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_ADDRESS);
try (final Repository repository = RepositoryManager.getRepository()) {
AccountData accountData = repository.getAccountRepository().getAccount(address);
// Not found?
if (accountData == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.ADDRESS_UNKNOWN);
// Valid address, so go ahead and blacklist it
boolean blacklisted = ResourceListManager.getInstance().isAddressInBlacklist(address);
return blacklisted ? "true" : "false";
} catch (DataException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
}
} }
} }

17
src/main/java/org/qortal/list/ResourceList.java

@ -21,7 +21,7 @@ public class ResourceList {
private String category; private String category;
private String resourceName; private String resourceName;
private List<String> list; private List<String> list = new ArrayList<>();
/** /**
* ResourceList * ResourceList
@ -36,7 +36,6 @@ public class ResourceList {
public ResourceList(String category, String resourceName) throws IOException { public ResourceList(String category, String resourceName) throws IOException {
this.category = category; this.category = category;
this.resourceName = resourceName; this.resourceName = resourceName;
this.list = new ArrayList<>();
this.load(); this.load();
} }
@ -45,7 +44,7 @@ public class ResourceList {
private Path getFilePath() { private Path getFilePath() {
String pathString = String.format("%s%s%s_%s.json", Settings.getInstance().getListsPath(), String pathString = String.format("%s%s%s_%s.json", Settings.getInstance().getListsPath(),
File.separator, this.resourceName, this.category); File.separator, this.category, this.resourceName);
return Paths.get(pathString); return Paths.get(pathString);
} }
@ -154,4 +153,16 @@ public class ResourceList {
return ResourceList.listToJSONString(this.list); return ResourceList.listToJSONString(this.list);
} }
public String getCategory() {
return this.category;
}
public String getResourceName() {
return this.resourceName;
}
public String toString() {
return String.format("%s %s", this.category, this.resourceName);
}
} }

91
src/main/java/org/qortal/list/ResourceListManager.java

@ -4,92 +4,125 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
public class ResourceListManager { public class ResourceListManager {
private static final Logger LOGGER = LogManager.getLogger(ResourceListManager.class); private static final Logger LOGGER = LogManager.getLogger(ResourceListManager.class);
private static ResourceListManager instance; private static ResourceListManager instance;
private ResourceList addressBlacklist; private List<ResourceList> lists = new ArrayList<>();
public ResourceListManager() { public ResourceListManager() {
try {
this.addressBlacklist = new ResourceList("blacklist", "address");
} catch (IOException e) {
LOGGER.info("Error while loading address blacklist. Blocking is currently unavailable.");
}
} }
public static synchronized ResourceListManager getInstance() { public static synchronized ResourceListManager getInstance() {
if (instance == null) { if (instance == null) {
instance = new ResourceListManager(); instance = new ResourceListManager();
} }
return instance; return instance;
} }
public boolean addAddressToBlacklist(String address, boolean save) { private ResourceList getList(String category, String resourceName) {
for (ResourceList list : this.lists) {
if (Objects.equals(list.getCategory(), category) &&
Objects.equals(list.getResourceName(), resourceName)) {
return list;
}
}
// List doesn't exist in array yet, so create it
// This will load any existing data from the filesystem
try {
ResourceList list = new ResourceList(category, resourceName);
this.lists.add(list);
return list;
} catch (IOException e) {
LOGGER.info("Unable to load or create list {} {}: {}", category, resourceName, e.getMessage());
return null;
}
}
public boolean addToList(String category, String resourceName, String item, boolean save) {
ResourceList list = this.getList(category, resourceName);
if (list == null) {
return false;
}
try { try {
this.addressBlacklist.add(address); list.add(item);
if (save) { if (save) {
this.addressBlacklist.save(); list.save();
} }
return true; return true;
} catch (IllegalStateException | IOException e) { } catch (IllegalStateException | IOException e) {
LOGGER.info("Unable to add address to blacklist", e); LOGGER.info(String.format("Unable to add item %s to list %s", item, list), e);
return false; return false;
} }
} }
public boolean removeAddressFromBlacklist(String address, boolean save) { public boolean removeFromList(String category, String resourceName, String item, boolean save) {
ResourceList list = this.getList(category, resourceName);
if (list == null) {
return false;
}
try { try {
this.addressBlacklist.remove(address); list.remove(item);
if (save) { if (save) {
this.addressBlacklist.save(); list.save();
} }
return true; return true;
} catch (IllegalStateException | IOException e) { } catch (IllegalStateException | IOException e) {
LOGGER.info("Unable to remove address from blacklist", e); LOGGER.info(String.format("Unable to remove item %s from list %s", item, list), e);
return false; return false;
} }
} }
public boolean isAddressInBlacklist(String address) { public boolean listContains(String category, String resourceName, String address) {
if (this.addressBlacklist == null) { ResourceList list = this.getList(category, resourceName);
if (list == null) {
return false; return false;
} }
return this.addressBlacklist.contains(address); return list.contains(address);
} }
public void saveBlacklist() { public void saveList(String category, String resourceName) {
if (this.addressBlacklist == null) { ResourceList list = this.getList(category, resourceName);
if (list == null) {
return; return;
} }
try { try {
this.addressBlacklist.save(); list.save();
} catch (IOException e) { } catch (IOException e) {
LOGGER.info("Unable to save blacklist - reverting back to last saved state"); LOGGER.info("Unable to save list {} - reverting back to last saved state", list);
this.addressBlacklist.revert(); list.revert();
} }
} }
public void revertBlacklist() { public void revertList(String category, String resourceName) {
if (this.addressBlacklist == null) { ResourceList list = this.getList(category, resourceName);
if (list == null) {
return; return;
} }
this.addressBlacklist.revert(); list.revert();
} }
public String getBlacklistJSONString() { public String getJSONStringForList(String category, String resourceName) {
if (this.addressBlacklist == null) { ResourceList list = this.getList(category, resourceName);
if (list == null) {
return null; return null;
} }
return this.addressBlacklist.getJSONString(); return list.getJSONString();
} }
} }

2
src/main/java/org/qortal/transaction/ChatTransaction.java

@ -146,7 +146,7 @@ public class ChatTransaction extends Transaction {
// Check for blacklisted author by address // Check for blacklisted author by address
ResourceListManager listManager = ResourceListManager.getInstance(); ResourceListManager listManager = ResourceListManager.getInstance();
if (listManager.isAddressInBlacklist(this.chatTransactionData.getSender())) { if (listManager.listContains("blacklist", "address", this.chatTransactionData.getSender())) {
return ValidationResult.ADDRESS_IN_BLACKLIST; return ValidationResult.ADDRESS_IN_BLACKLIST;
} }

Loading…
Cancel
Save