forked from Qortal/qortal
Merge branch 'master' of github.com:Qortal/qortal
This commit is contained in:
commit
a0ed3f53a4
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>3.0.2</version>
|
<version>3.0.4</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
@ -7,14 +7,13 @@ import java.nio.file.Path;
|
|||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||||
|
import org.qortal.api.ApiKey;
|
||||||
import org.qortal.api.ApiRequest;
|
import org.qortal.api.ApiRequest;
|
||||||
import org.qortal.controller.AutoUpdate;
|
import org.qortal.controller.AutoUpdate;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
@ -70,14 +69,40 @@ public class ApplyUpdate {
|
|||||||
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
|
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
|
||||||
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
|
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
|
||||||
|
|
||||||
|
// The /admin/stop endpoint requires an API key, which may or may not be already generated
|
||||||
|
boolean apiKeyNewlyGenerated = false;
|
||||||
|
ApiKey apiKey = null;
|
||||||
|
try {
|
||||||
|
apiKey = new ApiKey();
|
||||||
|
if (!apiKey.generated()) {
|
||||||
|
apiKey.generate();
|
||||||
|
apiKeyNewlyGenerated = true;
|
||||||
|
LOGGER.info("Generated API key");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.info("Error loading API key: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create GET params
|
||||||
|
Map<String, String> params = new HashMap<>();
|
||||||
|
if (apiKey != null) {
|
||||||
|
params.put("apiKey", apiKey.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to stop the node
|
||||||
int attempt;
|
int attempt;
|
||||||
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||||
final int attemptForLogging = attempt;
|
final int attemptForLogging = attempt;
|
||||||
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
|
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
|
||||||
String response = ApiRequest.perform(baseUri + "admin/stop", null);
|
String response = ApiRequest.perform(baseUri + "admin/stop", params);
|
||||||
if (response == null)
|
if (response == null) {
|
||||||
// No response - consider node shut down
|
// No response - consider node shut down
|
||||||
|
if (apiKeyNewlyGenerated) {
|
||||||
|
// API key was newly generated for this auto update, so we need to remove it
|
||||||
|
ApplyUpdate.removeGeneratedApiKey();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
LOGGER.info(() -> String.format("Response from API: %s", response));
|
LOGGER.info(() -> String.format("Response from API: %s", response));
|
||||||
|
|
||||||
@ -89,6 +114,11 @@ public class ApplyUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (apiKeyNewlyGenerated) {
|
||||||
|
// API key was newly generated for this auto update, so we need to remove it
|
||||||
|
ApplyUpdate.removeGeneratedApiKey();
|
||||||
|
}
|
||||||
|
|
||||||
if (attempt == MAX_ATTEMPTS) {
|
if (attempt == MAX_ATTEMPTS) {
|
||||||
LOGGER.error("Failed to shutdown node - giving up");
|
LOGGER.error("Failed to shutdown node - giving up");
|
||||||
return false;
|
return false;
|
||||||
@ -97,6 +127,19 @@ public class ApplyUpdate {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void removeGeneratedApiKey() {
|
||||||
|
try {
|
||||||
|
LOGGER.info("Removing newly generated API key...");
|
||||||
|
|
||||||
|
// Delete the API key since it was only generated for this auto update
|
||||||
|
ApiKey apiKey = new ApiKey();
|
||||||
|
apiKey.delete();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void replaceJar() {
|
private static void replaceJar() {
|
||||||
// Assuming current working directory contains the JAR files
|
// Assuming current working directory contains the JAR files
|
||||||
Path realJar = Paths.get(JAR_FILENAME);
|
Path realJar = Paths.get(JAR_FILENAME);
|
||||||
|
@ -81,6 +81,15 @@ public class ApiKey {
|
|||||||
writer.close();
|
writer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void delete() throws IOException {
|
||||||
|
this.apiKey = null;
|
||||||
|
|
||||||
|
Path filePath = this.getFilePath();
|
||||||
|
if (Files.exists(filePath)) {
|
||||||
|
Files.delete(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean generated() {
|
public boolean generated() {
|
||||||
return (this.apiKey != null);
|
return (this.apiKey != null);
|
||||||
|
@ -575,7 +575,11 @@ public class ArbitraryResource {
|
|||||||
@PathParam("name") String name,
|
@PathParam("name") String name,
|
||||||
@QueryParam("filepath") String filepath,
|
@QueryParam("filepath") String filepath,
|
||||||
@QueryParam("rebuild") boolean rebuild) {
|
@QueryParam("rebuild") boolean rebuild) {
|
||||||
|
|
||||||
|
// Authentication can be bypassed in the settings, for those running public QDN nodes
|
||||||
|
if (!Settings.getInstance().isQDNAuthBypassEnabled()) {
|
||||||
Security.checkApiCallAllowed(request);
|
Security.checkApiCallAllowed(request);
|
||||||
|
}
|
||||||
|
|
||||||
return this.download(service, name, null, filepath, rebuild);
|
return this.download(service, name, null, filepath, rebuild);
|
||||||
}
|
}
|
||||||
@ -604,7 +608,11 @@ public class ArbitraryResource {
|
|||||||
@PathParam("identifier") String identifier,
|
@PathParam("identifier") String identifier,
|
||||||
@QueryParam("filepath") String filepath,
|
@QueryParam("filepath") String filepath,
|
||||||
@QueryParam("rebuild") boolean rebuild) {
|
@QueryParam("rebuild") boolean rebuild) {
|
||||||
|
|
||||||
|
// Authentication can be bypassed in the settings, for those running public QDN nodes
|
||||||
|
if (!Settings.getInstance().isQDNAuthBypassEnabled()) {
|
||||||
Security.checkApiCallAllowed(request);
|
Security.checkApiCallAllowed(request);
|
||||||
|
}
|
||||||
|
|
||||||
return this.download(service, name, identifier, filepath, rebuild);
|
return this.download(service, name, identifier, filepath, rebuild);
|
||||||
}
|
}
|
||||||
@ -1049,6 +1057,10 @@ public class ArbitraryResource {
|
|||||||
// This is a single file resource
|
// This is a single file resource
|
||||||
filepath = files[0];
|
filepath = files[0];
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA,
|
||||||
|
"filepath is required for resources containing more than one file");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: limit file size that can be read into memory
|
// TODO: limit file size that can be read into memory
|
||||||
|
@ -6,6 +6,7 @@ import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType;
|
|||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||||
|
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.list.ResourceListManager;
|
import org.qortal.list.ResourceListManager;
|
||||||
@ -116,6 +117,9 @@ public class ArbitraryDataResource {
|
|||||||
// Also delete cached data for the entire resource
|
// Also delete cached data for the entire resource
|
||||||
this.deleteCache();
|
this.deleteCache();
|
||||||
|
|
||||||
|
// Invalidate the hosted transactions cache as we have removed an item
|
||||||
|
ArbitraryDataStorageManager.getInstance().invalidateHostedTransactionsCache();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (DataException | IOException e) {
|
} catch (DataException | IOException e) {
|
||||||
|
@ -51,7 +51,7 @@ public class BlockMinter extends Thread {
|
|||||||
// Min account level to submit blocks
|
// Min account level to submit blocks
|
||||||
// This is an unvalidated version of Blockchain.minAccountLevelToMint
|
// This is an unvalidated version of Blockchain.minAccountLevelToMint
|
||||||
// and exists only to reduce block candidates by default.
|
// and exists only to reduce block candidates by default.
|
||||||
private static int MIN_LEVEL_FOR_BLOCK_SUBMISSION = 3;
|
private static int MIN_LEVEL_FOR_BLOCK_SUBMISSION = 6;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ResourceList {
|
public class ResourceList {
|
||||||
@ -20,7 +21,7 @@ public class ResourceList {
|
|||||||
private static final Logger LOGGER = LogManager.getLogger(ResourceList.class);
|
private static final Logger LOGGER = LogManager.getLogger(ResourceList.class);
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private List<String> list = new ArrayList<>();
|
private List<String> list = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResourceList
|
* ResourceList
|
||||||
|
@ -5,6 +5,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ 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 List<ResourceList> lists = new ArrayList<>();
|
private List<ResourceList> lists = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
|
||||||
public ResourceListManager() {
|
public ResourceListManager() {
|
||||||
|
@ -330,7 +330,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
bindParams.add(name);
|
bindParams.add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.append(" GROUP BY name, service, identifier ORDER BY name");
|
sql.append(" GROUP BY name, service, identifier ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD");
|
||||||
|
|
||||||
if (reverse != null && reverse) {
|
if (reverse != null && reverse) {
|
||||||
sql.append(" DESC");
|
sql.append(" DESC");
|
||||||
@ -401,7 +401,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
bindParams.add(queryWildcard);
|
bindParams.add(queryWildcard);
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.append(" GROUP BY name, service, identifier ORDER BY name");
|
sql.append(" GROUP BY name, service, identifier ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD");
|
||||||
|
|
||||||
if (reverse != null && reverse) {
|
if (reverse != null && reverse) {
|
||||||
sql.append(" DESC");
|
sql.append(" DESC");
|
||||||
@ -465,7 +465,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
|
|||||||
sql.append(" AND (identifier = ? OR (? IS NULL))");
|
sql.append(" AND (identifier = ? OR (? IS NULL))");
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.append(" GROUP BY name ORDER BY name");
|
sql.append(" GROUP BY name ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD");
|
||||||
|
|
||||||
if (reverse != null && reverse) {
|
if (reverse != null && reverse) {
|
||||||
sql.append(" DESC");
|
sql.append(" DESC");
|
||||||
|
@ -308,6 +308,9 @@ public class Settings {
|
|||||||
/** Maximum total size of hosted data, in bytes. Unlimited if null */
|
/** Maximum total size of hosted data, in bytes. Unlimited if null */
|
||||||
private Long maxStorageCapacity = null;
|
private Long maxStorageCapacity = null;
|
||||||
|
|
||||||
|
/** Whether to serve QDN data without authentication */
|
||||||
|
private boolean qdnAuthBypassEnabled = false;
|
||||||
|
|
||||||
// Domain mapping
|
// Domain mapping
|
||||||
public static class DomainMap {
|
public static class DomainMap {
|
||||||
private String domain;
|
private String domain;
|
||||||
@ -884,4 +887,8 @@ public class Settings {
|
|||||||
public Long getMaxStorageCapacity() {
|
public Long getMaxStorageCapacity() {
|
||||||
return this.maxStorageCapacity;
|
return this.maxStorageCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isQDNAuthBypassEnabled() {
|
||||||
|
return this.qdnAuthBypassEnabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ package org.qortal.test.api;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.api.resource.AdminResource;
|
import org.qortal.api.resource.AdminResource;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.test.common.ApiCommon;
|
import org.qortal.test.common.ApiCommon;
|
||||||
import org.qortal.test.common.Common;
|
import org.qortal.test.common.Common;
|
||||||
|
|
||||||
@ -29,7 +31,10 @@ public class AdminApiTests extends ApiCommon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSummary() {
|
public void testSummary() throws IllegalAccessException {
|
||||||
|
// Set localAuthBypassEnabled to true, since we don't need to test authentication here
|
||||||
|
FieldUtils.writeField(Settings.getInstance(), "localAuthBypassEnabled", true, true);
|
||||||
|
|
||||||
assertNotNull(this.adminResource.summary("testApiKey"));
|
assertNotNull(this.adminResource.summary("testApiKey"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,5 @@
|
|||||||
"tempDataPath": "data-test/_temp",
|
"tempDataPath": "data-test/_temp",
|
||||||
"listsPath": "lists-test",
|
"listsPath": "lists-test",
|
||||||
"storagePolicy": "FOLLOWED_OR_VIEWED",
|
"storagePolicy": "FOLLOWED_OR_VIEWED",
|
||||||
"maxStorageCapacity": 104857600,
|
"maxStorageCapacity": 104857600
|
||||||
"localAuthBypassEnabled": true
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user