From a55fc4fff93d4df75bbe4c2e7b66eda5d62a2637 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 25 Oct 2021 18:58:33 +0100 Subject: [PATCH] When validating an ARBITRARY transaction, ensure that the supplied name exists and is registered to the account that is signing the transaction. This ensures that only the owner of a name is able to update data associated with that name. Note that this doesn't take into account the ability for group members to update a resource, so this will need modifying when that feature is ultimately introduced (likely after v3.0) --- .../transaction/ArbitraryTransaction.java | 17 ++++++ .../test/arbitrary/ArbitraryDataTests.java | 59 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java index 3fc8356b..0c6417fd 100644 --- a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java +++ b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java @@ -2,12 +2,14 @@ package org.qortal.transaction; import java.math.BigInteger; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import org.qortal.account.Account; import org.qortal.crypto.Crypto; import org.qortal.crypto.MemoryPoW; import org.qortal.data.PaymentData; +import org.qortal.data.naming.NameData; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.TransactionData; import org.qortal.payment.Payment; @@ -147,6 +149,21 @@ public class ArbitraryTransaction extends Transaction { } } + // Check name if one has been included + if (arbitraryTransactionData.getName() != null) { + NameData nameData = this.repository.getNameRepository().fromName(arbitraryTransactionData.getName()); + + // Check the name is registered + if (nameData == null) { + return ValidationResult.NAME_DOES_NOT_EXIST; + } + + // Check that the transaction signer owns the name + if (!Objects.equals(this.getCreator().getAddress(), nameData.getOwner())) { + return ValidationResult.INVALID_NAME_OWNER; + } + } + // Wrap and delegate final payment validity checks to Payment class return new Payment(this.repository).isValid(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), arbitraryTransactionData.getFee()); diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataTests.java index 9a7b2763..27761daf 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryDataTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryDataTests.java @@ -10,12 +10,14 @@ import org.qortal.arbitrary.ArbitraryDataTransactionBuilder; import org.qortal.arbitrary.metadata.ArbitraryDataMetadataPatch; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData.*; +import org.qortal.data.transaction.RegisterNameTransactionData; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; import org.qortal.test.common.BlockUtils; import org.qortal.test.common.Common; import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; import org.qortal.transaction.Transaction; import org.qortal.utils.Base58; @@ -41,6 +43,10 @@ public class ArbitraryDataTests extends Common { String name = "TEST"; // Can be anything for this test Service service = Service.WEBSITE; // Can be anything for this test + // Register the name to Alice + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + TransactionUtils.signAndMint(repository, transactionData, alice); + // Create PUT transaction Path path1 = Paths.get("src/test/resources/arbitrary/demo1"); this.createAndMintTxn(repository, publicKey58, path1, name, Method.PUT, service, alice); @@ -98,6 +104,59 @@ public class ArbitraryDataTests extends Common { } } + @Test + public void testNameDoesNotExist() throws DataException, IOException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String publicKey58 = Base58.encode(alice.getPublicKey()); + String name = "TEST"; // Can be anything for this test + Service service = Service.WEBSITE; // Can be anything for this test + + // Ensure the name doesn't exist + assertNull(repository.getNameRepository().fromName(name)); + + // Create PUT transaction, ensuring that an exception is thrown + try { + Path path1 = Paths.get("src/test/resources/arbitrary/demo1"); + this.createAndMintTxn(repository, publicKey58, path1, name, Method.PUT, service, alice); + fail("Creating transaction should fail due to the name being unregistered"); + + } catch (DataException expectedException) { + assertEquals("Arbitrary transaction invalid: NAME_DOES_NOT_EXIST", expectedException.getMessage()); + } + } + } + + @Test + public void testUpdateResourceOwnedByAnotherCreator() throws DataException, IOException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String name = "TEST"; // Can be anything for this test + Service service = Service.WEBSITE; // Can be anything for this test + + // Register the name to Alice + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Create PUT transaction + Path path1 = Paths.get("src/test/resources/arbitrary/demo1"); + this.createAndMintTxn(repository, Base58.encode(alice.getPublicKey()), path1, name, Method.PUT, service, alice); + + // Bob attempts to update Alice's data + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + // Create PATCH transaction, ensuring that an exception is thrown + try { + Path path2 = Paths.get("src/test/resources/arbitrary/demo2"); + this.createAndMintTxn(repository, Base58.encode(bob.getPublicKey()), path2, name, Method.PATCH, service, bob); + fail("Creating transaction should fail due to the name being registered to Alice instead of Bob"); + + } catch (DataException expectedException) { + assertEquals("Arbitrary transaction invalid: INVALID_NAME_OWNER", expectedException.getMessage()); + } + } + } + private void createAndMintTxn(Repository repository, String publicKey58, Path path, String name, Method method, Service service, PrivateKeyAccount account) throws DataException {