diff --git a/bubble-server/src/main/java/bubble/dao/account/AccountPolicyDAO.java b/bubble-server/src/main/java/bubble/dao/account/AccountPolicyDAO.java index 80527bdd..a627f11c 100644 --- a/bubble-server/src/main/java/bubble/dao/account/AccountPolicyDAO.java +++ b/bubble-server/src/main/java/bubble/dao/account/AccountPolicyDAO.java @@ -8,8 +8,7 @@ import bubble.model.account.AccountPolicy; import org.cobbzilla.wizard.validation.ValidationResult; import org.springframework.stereotype.Repository; -import java.util.List; - +import static bubble.model.account.AccountDeletionPolicy.full_delete; import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.wizard.resources.ResourceUtil.invalidEx; @@ -32,8 +31,19 @@ public class AccountPolicyDAO extends AccountOwnedEntityDAO { } public AccountPolicy findSingleByAccount(String accountUuid) { - final List found = findByAccount(accountUuid); - return found.isEmpty() ? create(new AccountPolicy().setAccount(accountUuid)) : found.size() > 1 ? die("findSingleByAccount: "+found.size()+" found!") : found.get(0); + final var found = findByAccount(accountUuid); + if (found.size() == 1) return found.get(0); + + if (found.size() > 1) { + die("findSingleByAccount: More than 1 policy found for account " + accountUuid + " - " + found.size()); + } + + // If there's no policy, create one. Note that is account is marked as deleted, the new policy will be with full + // deletion set in. + final var newPolicy = new AccountPolicy().setAccount(accountUuid); + final var account = getConfiguration().getBean(AccountDAO.class).findById(accountUuid); + if (account.deleted()) newPolicy.setDeletionPolicy(full_delete); + return create(newPolicy); } } diff --git a/bubble-server/src/main/java/bubble/resources/account/AuthResource.java b/bubble-server/src/main/java/bubble/resources/account/AuthResource.java index e8154b12..3e32f781 100644 --- a/bubble-server/src/main/java/bubble/resources/account/AuthResource.java +++ b/bubble-server/src/main/java/bubble/resources/account/AuthResource.java @@ -306,7 +306,7 @@ public class AuthResource { if (!request.hasName()) return invalid("err.name.required", "name is required"); if (!request.hasPassword()) return invalid("err.password.required", "password is required"); final Account account = accountDAO.findByName(request.getName()); - if (account == null) return notFound(request.getName()); + if (account == null || account.deleted()) return notFound(request.getName()); if (!account.getHashedPassword().isCorrectPassword(request.getPassword())) { return notFound(request.getName()); } diff --git a/bubble-server/src/main/resources/logback.xml b/bubble-server/src/main/resources/logback.xml index fadc1ef9..e3f4bc31 100644 --- a/bubble-server/src/main/resources/logback.xml +++ b/bubble-server/src/main/resources/logback.xml @@ -28,6 +28,7 @@ + diff --git a/bubble-server/src/test/java/bubble/test/dev/BlankDevServerTest.java b/bubble-server/src/test/java/bubble/test/dev/BlankDevServerTest.java index a3bbd091..66e60fad 100644 --- a/bubble-server/src/test/java/bubble/test/dev/BlankDevServerTest.java +++ b/bubble-server/src/test/java/bubble/test/dev/BlankDevServerTest.java @@ -10,6 +10,7 @@ import org.cobbzilla.wizard.server.RestServerLifecycleListener; import org.cobbzilla.wizard.server.listener.FlywayMigrationListener; import org.junit.Test; +import java.net.URL; import java.util.ArrayList; import java.util.Collection; @@ -33,7 +34,8 @@ public class BlankDevServerTest extends NewBlankDevServerTest { } @Test public void runBlankServer () throws Exception { - log.info("runBlankServer: Bubble API server started and model initialized. You may now begin testing."); + log.info("runBlankServer: Bubble API server started and model initialized. You may now begin testing on port: " + + new URL(this.getApi().getBaseUri()).getPort()); sleep(DAYS.toMillis(30), "running dev server"); } diff --git a/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java b/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java index f56b6ece..792c8ea5 100644 --- a/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java +++ b/bubble-server/src/test/java/bubble/test/dev/DevServerTest.java @@ -11,6 +11,8 @@ import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.server.RestServer; import org.junit.Test; +import java.net.URL; + import static java.util.concurrent.TimeUnit.DAYS; import static org.cobbzilla.util.system.Sleep.sleep; @@ -31,7 +33,8 @@ public class DevServerTest extends ActivatedBubbleModelTestBase { } @Test public void runDevServer () throws Exception { - log.info("runDevServer: Bubble API server started and model initialized. You may now begin testing."); + log.info("runDevServer: Bubble API server started and model initialized. You may now begin testing on port: " + + new URL(this.getApi().getBaseUri()).getPort()); sleep(DAYS.toMillis(30), "running dev server"); } diff --git a/bubble-server/src/test/java/bubble/test/dev/NewBlankDevServerTest.java b/bubble-server/src/test/java/bubble/test/dev/NewBlankDevServerTest.java index daff68bb..a58b09b7 100644 --- a/bubble-server/src/test/java/bubble/test/dev/NewBlankDevServerTest.java +++ b/bubble-server/src/test/java/bubble/test/dev/NewBlankDevServerTest.java @@ -11,6 +11,8 @@ import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.server.RestServer; import org.junit.Test; +import java.net.URL; + import static java.util.concurrent.TimeUnit.DAYS; import static org.cobbzilla.util.string.StringUtil.safeParseInt; import static org.cobbzilla.util.system.Sleep.sleep; @@ -35,7 +37,8 @@ public class NewBlankDevServerTest extends BubbleModelTestBase { } @Test public void runBlankServer () throws Exception { - log.info("runBlankServer: Bubble API server started and model initialized. You may now begin testing."); + log.info("runBlankServer: Bubble API server started and model initialized. You may now begin testing on port: " + + new URL(this.getApi().getBaseUri()).getPort()); sleep(DAYS.toMillis(30), "running dev server"); } diff --git a/bubble-server/src/test/java/bubble/test/system/AccountDeletionTest.java b/bubble-server/src/test/java/bubble/test/system/AccountDeletionTest.java new file mode 100644 index 00000000..f389155a --- /dev/null +++ b/bubble-server/src/test/java/bubble/test/system/AccountDeletionTest.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2020 Bubble, Inc. All rights reserved. + * For personal (non-commercial) use, see license: https://bubblev.com/bubble-license/ + */ +package bubble.test.system; + +import bubble.test.ActivatedBubbleModelTestBase; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; + +@Slf4j +public class AccountDeletionTest extends ActivatedBubbleModelTestBase { + + @Override protected String getManifest() { return "manifest-test"; } + + @Test public void testFullAccountDeletion() throws Exception { modelTest("account_deletion/full_delete_account"); } + @Test public void testBlockAccountDeletion() throws Exception { modelTest("account_deletion/block_delete_account"); } + +} diff --git a/bubble-server/src/test/java/bubble/test/system/AuthTest.java b/bubble-server/src/test/java/bubble/test/system/AuthTest.java index e333ce36..d1c2d5b2 100644 --- a/bubble-server/src/test/java/bubble/test/system/AuthTest.java +++ b/bubble-server/src/test/java/bubble/test/system/AuthTest.java @@ -24,7 +24,6 @@ public class AuthTest extends ActivatedBubbleModelTestBase { accountDAO.update(rootUser.setHashedPassword(new HashedPassword(ROOT_PASSWORD))); } - @Test public void testAccountDeletion () throws Exception { modelTest("auth/delete_account"); } @Test public void testBasicAuth () throws Exception { modelTest("auth/basic_auth"); } @Test public void testAccountCrud () throws Exception { modelTest("auth/account_crud"); } @Test public void testDeviceCrud () throws Exception { modelTest("auth/device_crud"); } diff --git a/bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account.json b/bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account.json new file mode 100644 index 00000000..3d6269ae --- /dev/null +++ b/bubble-server/src/test/resources/models/tests/account_deletion/block_delete_account.json @@ -0,0 +1,97 @@ +[ + { + "comment": "activate service, create account, login for block_delete", + "include": "new_account", + "params": { + "username": "user-to-partially-delete", + "password": "foobar1!", + "email": "user-partially-account-deletion@example.com", + "verifyEmail": "true" + } + }, + + { + "comment": "look up account policy for block_delete", + "request": { "uri": "users/{{user.uuid}}/policy" }, + "response": { + "store": "policy", + "check": [ + {"condition": "json.getAccountContacts() != null"}, + {"condition": "json.getAccountContacts().length == 1"} + ] + } + }, + + { + "comment": "set deletion policy to block_delete", + "request": { + "uri": "users/{{user.uuid}}/policy", + "data": "policy", + "entity": { + "deletionPolicy": "block_delete" + } + }, + "response": { + "store": "policy", + "check": [ + {"condition": "json.getDeletionPolicy().name() == 'block_delete'"} + ] + } + }, + + { + "comment": "as root, block delete account", + "request": { + "session": "rootSession", + "uri": "users/{{user.uuid}}", + "method": "delete" + } + }, + + { + "comment": "lookup user, expect that it is still there, just empty", + "request": { "uri": "users/{{user.uuid}}" }, + "response": { + "check": [ + { "condition": "json.getUuid() == user.getUuid()" }, + { "condition": "json.getName() == user.getName()" }, + { "condition": "json.deleted()" } + ] + } + }, + + { + "comment": "try logging in as deleted user - failing now", + "request": { + "session": "new", + "uri": "auth/login", + "entity": { + "name": "{{user.name}}", + "password": "foobar1!" + } + }, + "response": { "status": 404 } + }, + + { + "comment": "as root, look up account policy again - it should recreated for this object with full deletion policy", + "request": { "session": "rootSession", "uri": "users/{{user.uuid}}/policy" }, + "response": { "check": [{ "condition": "json.getDeletionPolicy().name() == 'full_delete'" }] } + }, + + { + "comment": "try deleting the same account again - expect fully deletion this time even without policy", + "request": { "uri": "users/{{user.uuid}}", "method": "delete" } + }, + + { + "comment": "lookup user, expect there's no such user now", + "request": { "uri": "users/{{user.uuid}}" }, + "response": { "status": 404 } + }, + { + "comment": "as root, look up account policy again - not found, and account uuid reported as not found resource", + "request": { "session": "rootSession", "uri": "users/{{user.uuid}}/policy" }, + "response": { "status": 404, "check": [{ "condition": "json.get('resource') == user.getUuid()" }] } + } +] diff --git a/bubble-server/src/test/resources/models/tests/auth/delete_account.json b/bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account.json similarity index 73% rename from bubble-server/src/test/resources/models/tests/auth/delete_account.json rename to bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account.json index 36f00487..9e0468c9 100644 --- a/bubble-server/src/test/resources/models/tests/auth/delete_account.json +++ b/bubble-server/src/test/resources/models/tests/account_deletion/full_delete_account.json @@ -12,7 +12,7 @@ { "comment": "look up account policy", - "request": { "uri": "users/user-to-delete/policy" }, + "request": { "uri": "users/{{user.uuid}}/policy" }, "response": { "store": "policy", "check": [ @@ -25,7 +25,7 @@ { "comment": "set deletion policy to full_delete", "request": { - "uri": "users/user-to-delete/policy", + "uri": "users/{{user.uuid}}/policy", "data": "policy", "entity": { "deletionPolicy": "full_delete" @@ -39,11 +39,17 @@ } }, + { + "comment": "lookup user - just checking it is the one for deletion", + "request": { "uri": "users/{{user.uuid}}" }, + "response": { "check": [{ "condition": "json.getName() == 'user-to-delete'" }] } + }, + { "comment": "as root, delete account", "request": { "session": "rootSession", - "uri": "users/user-to-delete", + "uri": "users/{{user.uuid}}", "method": "delete" } }, @@ -51,8 +57,8 @@ { "comment": "lookup user, expect not found", "request": { - "uri": "users/user-to-delete" + "uri": "users/{{user.uuid}}" }, "response": { "status": 404 } } -] \ No newline at end of file +]