Initial version of the account removal plugin

The account plugin for Gerrit is designed to allow companies to
improve compliance with the GDPR requirements:

- Ability for an individual to display the personal information that
  Gerrit holds about him
- Ability to "self-remove" the personal information from Gerrit

NOTE: This plugin itself is not giving any GDPR certification or
      compliance. You would need to read carefully the EU law and
      apply to your Company context and organisation.

Change-Id: I64cdaee2803b63e05285b054cecde7be6b390149
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..bcaa2f0
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,35 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+load(
+    "//tools/bzl:plugin.bzl",
+    "gerrit_plugin",
+    "PLUGIN_DEPS",
+    "PLUGIN_TEST_DEPS",
+)
+
+gerrit_plugin(
+    name = "account",
+    srcs = glob(["src/main/java/**/*.java"]),
+    manifest_entries = [
+        "Gerrit-PluginName: account",
+        "Gerrit-Module: com.gerritforge.gerrit.plugins.account.Module",
+        "Gerrit-SshModule: com.gerritforge.gerrit.plugins.account.SshModule",
+    ],
+    resources = glob(["src/main/resources/**/*"]),
+)
+
+junit_tests(
+    name = "account_tests",
+    srcs = glob(["src/test/java/**/*.java"]),
+    tags = ["account"],
+    deps = [":account__plugin_test_deps"],
+)
+
+java_library(
+    name = "account__plugin_test_deps",
+    testonly = 1,
+    visibility = ["//visibility:public"],
+    exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+        ":account__plugin",
+        "@mockito//jar",
+    ],
+)
diff --git a/README.md b/README.md
index 95e5d56..6704c36 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,15 @@
-# gerrit-account-plugin
-Gerrit plugin to allow self-service management of accounts
+# Account management plugin for Gerrit Code Review
+
+A plugin that allows accounts to be deleted from Gerrit via an SSH command or
+REST API.
+
+## How to build
+
+To build this plugin you need to have Bazel and Gerrit source tree. See the
+[detailed instructions](/src/main/resources/Documentation/build.md) on how to build it.
+
+## Commands
+
+This plugin adds an new [SSH command](/src/main/resources/Documentation/cmd-delete.md)
+and a [REST API](/src/main/resources/Documentation/rest-api-accounts.md) for removing
+accounts from Gerrit.
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
new file mode 100644
index 0000000..43d72aa
--- /dev/null
+++ b/external_plugin_deps.bzl
@@ -0,0 +1,33 @@
+load("//tools/bzl:maven_jar.bzl", "maven_jar")
+
+def external_plugin_deps():
+  maven_jar(
+    name = "mockito",
+    artifact = "org.mockito:mockito-core:2.16.0",
+    sha1 = "a022ee494c753789a1e7cae75099de81d8a5cea6",
+    deps = [
+      "@byte_buddy//jar",
+      "@byte_buddy_agent//jar",
+      "@objenesis//jar",
+    ],
+  )
+
+  BYTE_BUDDY_VERSION = "1.7.9"
+
+  maven_jar(
+    name = "byte_buddy",
+    artifact = "net.bytebuddy:byte-buddy:" + BYTE_BUDDY_VERSION,
+    sha1 = "51218a01a882c04d0aba8c028179cce488bbcb58",
+  )
+
+  maven_jar(
+    name = "byte_buddy_agent",
+    artifact = "net.bytebuddy:byte-buddy-agent:" + BYTE_BUDDY_VERSION,
+    sha1 = "a6c65f9da7f467ee1f02ff2841ffd3155aee2fc9",
+  )
+
+  maven_jar(
+    name = "objenesis",
+    artifact = "org.objenesis:objenesis:2.6",
+    sha1 = "639033469776fd37c08358c6b92a4761feb2af4b",
+  )
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/AccountPersonalInformation.java b/src/main/java/com/gerritforge/gerrit/plugins/account/AccountPersonalInformation.java
new file mode 100644
index 0000000..a81f4c6
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/AccountPersonalInformation.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.google.gerrit.server.IdentifiedUser;
+import java.util.Set;
+
+public class AccountPersonalInformation {
+  public final String fullname;
+  public final String username;
+  public final Set<String> emails;
+
+  public AccountPersonalInformation(IdentifiedUser user) {
+    this.fullname = user.getName();
+    this.username = user.getUserName();
+    this.emails = user.getEmailAddresses();
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/AccountRemover.java b/src/main/java/com/gerritforge/gerrit/plugins/account/AccountRemover.java
new file mode 100644
index 0000000..cbf35fe
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/AccountRemover.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.google.inject.ImplementedBy;
+
+@ImplementedBy(GerritAccountRemover.class)
+public interface AccountRemover {
+
+  void removeAccount(int accountId) throws Exception;
+
+  boolean canDelete(int accountId);
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/AccountResourceFactory.java b/src/main/java/com/gerritforge/gerrit/plugins/account/AccountResourceFactory.java
new file mode 100644
index 0000000..b9b84fc
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/AccountResourceFactory.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.inject.Inject;
+
+public class AccountResourceFactory {
+  private final GenericFactory userFactory;
+
+  @Inject
+  public AccountResourceFactory(GenericFactory userFactory) {
+    this.userFactory = userFactory;
+  }
+
+  public AccountResource create(int accountId) {
+    return new AccountResource(userFactory.create(new Account.Id(accountId)));
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccount.java b/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccount.java
new file mode 100644
index 0000000..0f490c0
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccount.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.inject.Inject;
+
+public class DeleteAccount implements RestModifyView<AccountResource, DeleteAccount.Input> {
+  public static class Input {
+    public String accountName;
+
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((accountName == null) ? 0 : accountName.hashCode());
+      return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) return true;
+      if (obj == null) return false;
+      if (getClass() != obj.getClass()) return false;
+      Input other = (Input) obj;
+      if (accountName == null) {
+        if (other.accountName != null) return false;
+      } else if (!accountName.equals(other.accountName)) return false;
+      return true;
+    }
+  }
+
+  private final AccountRemover remover;
+
+  @Inject
+  public DeleteAccount(AccountRemover remover) {
+    this.remover = remover;
+  }
+
+  @Override
+  public Object apply(AccountResource resource, DeleteAccount.Input input)
+      throws AuthException, BadRequestException, ResourceConflictException, Exception {
+    boolean removed = false;
+
+    IdentifiedUser user = resource.getUser();
+    AccountPersonalInformation accountInfo = new AccountPersonalInformation(user);
+    int accountId = user.getAccountId().get();
+    assertDeletePermission(accountId);
+
+    if (input != null && input.accountName != null && user.getName().equals(input.accountName)) {
+      remover.removeAccount(accountId);
+      removed = true;
+    }
+
+    return new DeleteAccountResponse(removed, accountInfo);
+  }
+
+  private void assertDeletePermission(int accountId) throws AuthException {
+    if (!remover.canDelete(accountId)) {
+      throw new AuthException("not allowed to delete account " + accountId);
+    }
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccountCommand.java b/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccountCommand.java
new file mode 100644
index 0000000..186586a
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccountCommand.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gson.Gson;
+import com.google.inject.Inject;
+import java.io.PrintWriter;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@CommandMetaData(name = "delete", description = "Delete a specific account")
+public class DeleteAccountCommand extends SshCommand {
+  private static final Logger log = LoggerFactory.getLogger(DeleteAccountCommand.class);
+
+  @Argument(index = 0, required = true, metaVar = "ACCOUNT-ID", usage = "id of the account")
+  private int accountId;
+
+  @Option(
+    name = "--yes-really-delete",
+    metaVar = "ACCOUNT-NAME",
+    usage = "confirmation to delete the account"
+  )
+  private String accountName;
+
+  private final AccountResourceFactory accountFactory;
+  private final DeleteAccount deleteAccount;
+
+  @Inject
+  public DeleteAccountCommand(AccountResourceFactory accountFactory, DeleteAccount deleteAccount) {
+    this.accountFactory = accountFactory;
+    this.deleteAccount = deleteAccount;
+  }
+
+  @Override
+  protected void run() throws UnloggedFailure, Failure, Exception {
+    try {
+      AccountResource account = accountFactory.create(accountId);
+
+      DeleteAccount.Input input = new DeleteAccount.Input();
+      input.accountName = accountName;
+
+      DeleteAccountResponse resp = (DeleteAccountResponse) deleteAccount.apply(account, input);
+
+      @SuppressWarnings("resource")
+      PrintWriter out = resp.deleted ? stdout : stderr;
+      out.println("Account " + (resp.deleted ? "" : "NOT") + " deleted");
+      out.println(new Gson().toJson(resp.accountInfo));
+    } catch (Exception e) {
+      stderr.printf("FAILED (%s): %s\n", e.getClass().getName(), e.getMessage());
+      stderr.flush();
+      log.error("Unable to remove account %d", accountId, e);
+      die(e);
+    }
+  }
+
+  @VisibleForTesting
+  public void testRun(int accountId, String accountName) throws Exception {
+    this.accountId = accountId;
+    this.accountName = accountName;
+    run();
+  }
+
+  @VisibleForTesting
+  public void setPrintWriters(PrintWriter out, PrintWriter err) {
+    this.stdout = out;
+    this.stderr = err;
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccountResponse.java b/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccountResponse.java
new file mode 100644
index 0000000..97fb1dd
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/DeleteAccountResponse.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+public class DeleteAccountResponse {
+  public final boolean deleted;
+  public final AccountPersonalInformation accountInfo;
+
+  public DeleteAccountResponse(boolean deleted, AccountPersonalInformation accountInfo) {
+    this.deleted = deleted;
+    this.accountInfo = accountInfo;
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/GerritAccountRemover.java b/src/main/java/com/gerritforge/gerrit/plugins/account/GerritAccountRemover.java
new file mode 100644
index 0000000..bf1553b
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/GerritAccountRemover.java
@@ -0,0 +1,135 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.gerritforge.gerrit.plugins.account.permissions.DeleteAccountCapability;
+import com.gerritforge.gerrit.plugins.account.permissions.DeleteOwnAccountCapability;
+import com.google.gerrit.extensions.annotations.PluginName;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.access.PluginPermission;
+import com.google.gerrit.extensions.api.accounts.AccountApi;
+import com.google.gerrit.extensions.api.accounts.Accounts;
+import com.google.gerrit.extensions.common.EmailInfo;
+import com.google.gerrit.extensions.common.SshKeyInfo;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.account.PutName;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GerritAccountRemover implements AccountRemover {
+  private static final Logger log = LoggerFactory.getLogger(GerritAccountRemover.class);
+  private final Accounts accounts;
+  private final PutName putName;
+  private final AccountResourceFactory accountFactory;
+  private final PermissionBackend permissionBackend;
+  private final Provider<CurrentUser> userProvider;
+  private final String pluginName;
+
+  @Inject
+  public GerritAccountRemover(
+      GerritApi api,
+      PutName putName,
+      AccountResourceFactory accountFactory,
+      PermissionBackend permissionBackend,
+      Provider<CurrentUser> userProvider,
+      @PluginName String pluginName) {
+    this.accounts = api.accounts();
+    this.putName = putName;
+    this.accountFactory = accountFactory;
+    this.permissionBackend = permissionBackend;
+    this.userProvider = userProvider;
+    this.pluginName = pluginName;
+  }
+
+  @Override
+  public void removeAccount(int accountId) throws Exception {
+    AccountApi account = isMyAccount(accountId) ? accounts.self() : accounts.id(accountId);
+    removeAccount(account, accountId);
+  }
+
+  private boolean isMyAccount(int accountId) {
+    return userProvider.get().getAccountId().get() == accountId;
+  }
+
+  private AccountResource getAccountResource(int accountId) {
+    return isMyAccount(accountId)
+        ? new AccountResource(userProvider.get().asIdentifiedUser())
+        : accountFactory.create(accountId);
+  }
+
+  private void removeAccount(AccountApi account, int accountId) throws Exception {
+    removeAccountEmails(account);
+    removeAccountSshKeys(account);
+    removeExternalIds(account);
+    removeFullName(getAccountResource(accountId));
+    if (account.getActive()) {
+      account.setActive(false);
+    }
+  }
+
+  @Override
+  public boolean canDelete(int accountId) {
+    PermissionBackend.WithUser userPermission = permissionBackend.user(userProvider);
+    return userPermission.testOrFalse(
+            new PluginPermission(pluginName, DeleteAccountCapability.DELETE_ACCOUNT))
+        || (userPermission.testOrFalse(
+                new PluginPermission(pluginName, DeleteOwnAccountCapability.DELETE_OWN_ACCOUNT))
+            && isMyAccount(accountId));
+  }
+
+  private void removeFullName(AccountResource userRsc) throws Exception {
+    putName.apply(userRsc, new PutName.Input());
+  }
+
+  private void removeExternalIds(AccountApi account) throws RestApiException {
+    List<String> externalIds =
+        account
+            .getExternalIds()
+            .stream()
+            .map(eid -> eid.identity)
+            .filter(eid -> !eid.startsWith(ExternalId.SCHEME_USERNAME))
+            .filter(eid -> !eid.startsWith(ExternalId.SCHEME_UUID))
+            .filter(eid -> !eid.startsWith(ExternalId.SCHEME_GERRIT))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
+    if (externalIds.size() > 0) {
+      account.deleteExternalIds(externalIds);
+    }
+  }
+
+  private void removeAccountSshKeys(AccountApi account) throws RestApiException {
+    List<SshKeyInfo> accountKeys = account.listSshKeys();
+    for (SshKeyInfo sshKeyInfo : accountKeys) {
+      if (sshKeyInfo != null && sshKeyInfo.valid) {
+        account.deleteSshKey(sshKeyInfo.seq);
+      }
+    }
+  }
+
+  private void removeAccountEmails(AccountApi account) throws RestApiException {
+    for (EmailInfo email : account.getEmails()) {
+      account.deleteEmail(email.email);
+    }
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/Module.java b/src/main/java/com/gerritforge/gerrit/plugins/account/Module.java
new file mode 100644
index 0000000..b715154
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/Module.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import static com.google.gerrit.server.account.AccountResource.ACCOUNT_KIND;
+
+import com.gerritforge.gerrit.plugins.account.permissions.DeleteAccountCapability;
+import com.gerritforge.gerrit.plugins.account.permissions.DeleteOwnAccountCapability;
+import com.google.gerrit.extensions.annotations.Exports;
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.inject.AbstractModule;
+
+class Module extends AbstractModule {
+
+  @Override
+  protected void configure() {
+    install(
+        new RestApiModule() {
+          @Override
+          protected void configure() {
+            delete(ACCOUNT_KIND).to(DeleteAccount.class);
+          }
+        });
+
+    bind(CapabilityDefinition.class)
+        .annotatedWith(Exports.named(DeleteAccountCapability.DELETE_ACCOUNT))
+        .to(DeleteAccountCapability.class);
+    bind(CapabilityDefinition.class)
+        .annotatedWith(Exports.named(DeleteOwnAccountCapability.DELETE_OWN_ACCOUNT))
+        .to(DeleteOwnAccountCapability.class);
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/SshModule.java b/src/main/java/com/gerritforge/gerrit/plugins/account/SshModule.java
new file mode 100644
index 0000000..f5fdfc0
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/SshModule.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account;
+
+import com.google.gerrit.sshd.PluginCommandModule;
+
+public class SshModule extends PluginCommandModule {
+  @Override
+  protected void configureCommands() {
+    command(DeleteAccountCommand.class);
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/permissions/DeleteAccountCapability.java b/src/main/java/com/gerritforge/gerrit/plugins/account/permissions/DeleteAccountCapability.java
new file mode 100644
index 0000000..5ddbdd7
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/permissions/DeleteAccountCapability.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account.permissions;
+
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+
+public class DeleteAccountCapability extends CapabilityDefinition {
+  public static final String DELETE_ACCOUNT = "deleteAccount";
+
+  @Override
+  public String getDescription() {
+    return "Delete any account";
+  }
+}
diff --git a/src/main/java/com/gerritforge/gerrit/plugins/account/permissions/DeleteOwnAccountCapability.java b/src/main/java/com/gerritforge/gerrit/plugins/account/permissions/DeleteOwnAccountCapability.java
new file mode 100644
index 0000000..b97aece
--- /dev/null
+++ b/src/main/java/com/gerritforge/gerrit/plugins/account/permissions/DeleteOwnAccountCapability.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account.permissions;
+
+import com.google.gerrit.extensions.config.CapabilityDefinition;
+
+public class DeleteOwnAccountCapability extends CapabilityDefinition {
+  public static final String DELETE_OWN_ACCOUNT = "deleteOwnAccount";
+
+  @Override
+  public String getDescription() {
+    return "Delete user's own account";
+  }
+}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..ae2d9f0
--- /dev/null
+++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,47 @@
+Provides the ability to manage accounts.
+
+Gerrit accounts need at time some maintenance for troubleshooting
+purposes or for allowing people to "be forgotten" and removing
+all their associated identities and personal information.
+
+Allow admins and users to see what Gerrit holds about accounts in its
+repository and give the ability to remove any information by preserving
+the overall consistency with existing review data.
+
+Limitations
+-----------
+
+There are a few caveats:
+
+* Removal of accounts cannot be undone
+
+	This is an irreversible action, and should be taken with extreme
+	care. Backups of the accounts repository is strongly recommended.
+
+* You cannot physically delete accounts but make sure they become anonymous.
+
+	The removal of the accounts is only a logical operation. Gerrit will
+	keep the account id and some associated external ids. All the other information
+	that allows to associate a Gerrit account to a physical person will be removed.
+	The only requirement for Gerrit is to have a username with a corresponding account_id,
+	which will always be kept in the repository for consistency with existing
+	reviews.
+
+Replication of accounts deletions
+--------------------------------
+
+This plugin does not explicitly replicate any account deletions, but it triggers
+an event when an account is deleted. The [replication plugin]
+(https://gerrit-review.googlesource.com/#/admin/projects/plugins/replication)
+will do the job by triggering the replication of the associated ref updates on the
+All-Users repository.
+
+Access
+------
+
+To be allowed to delete arbitrary projects a user must be a member of a
+group that is granted the 'Delete Account' capability (provided by this
+plugin). Users can be enabled to remove their own accounts if they are member
+of a group that is granted the 'Delete Own Account' capability (provided by this
+plugin).
+
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
new file mode 100644
index 0000000..119c894
--- /dev/null
+++ b/src/main/resources/Documentation/build.md
@@ -0,0 +1,50 @@
+Build
+=====
+
+This plugin is built with Bazel in Gerrit tree mode.
+
+## Build in Gerrit tree
+
+Clone or link this plugin and the account/external_plugin_deps.bzl into the Gerrit's /plugins source
+tree.
+
+```
+  git clone https://gerrit.googlesource.com/gerrit
+  git clone https://github.com/GerritForge/account.git
+  cd gerrit/plugin
+  ln -s ../../gerrit-account-plugin account
+  rm -f external_plugin_deps.bzl
+  ln -s account/external_plugin_deps.bzl .
+```
+
+Then issue the bazel build command:
+
+```
+  bazel build plugins/account
+```
+
+The output is created in
+
+```
+  bazel-genfiles/plugins/account/account.jar
+```
+
+To execute the tests run:
+
+```
+  bazel test plugins/account:account_tests
+```
+
+or filtering using the comma separated tags:
+
+````
+  bazel test --test_tag_filters=account //...
+````
+
+This project can be imported into the Eclipse IDE.
+Add the plugin name to the `CUSTOM_PLUGINS` set in
+Gerrit core in `tools/bzl/plugins.bzl`, and execute:
+
+```
+  ./tools/eclipse/project.py
+```
diff --git a/src/main/resources/Documentation/cmd-delete.md b/src/main/resources/Documentation/cmd-delete.md
new file mode 100644
index 0000000..12b2c3c
--- /dev/null
+++ b/src/main/resources/Documentation/cmd-delete.md
@@ -0,0 +1,57 @@
+@PLUGIN@ delete
+===============
+
+NAME
+----
+@PLUGIN@ delete - Completely delete an account and all its associated external ids
+
+SYNOPSIS
+--------
+```
+ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ delete
+  [--yes-really-delete <ACCOUNT-NAME>]
+  <ACCOUNT-ID>
+```
+
+DESCRIPTION
+-----------
+Deletes an account from the Gerrit installation, removing the associated
+external ids.
+
+ACCESS
+------
+Caller must be a member of a group that is granted the 'Delete Account'
+capability (provided by this plugin), or granted the 'Delete Own Account' to
+be able to remove its own account.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+
+`--yes-really-delete`
+:	Actually perform the deletion. If omitted, the command
+	will just output information about the deletion and then
+	exit. 
+
+EXAMPLES
+--------
+See if you can delete an account:
+
+```
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ delete 100002
+```
+
+Completely delete an account:
+
+```
+  $ ssh -p @SSH_PORT@ @SSH_HOST@ @PLUGIN@ delete --yes-really-delete "'John Doe'" 100002
+```
+
+
+SEE ALSO
+--------
+
+* [Access Control](../../../Documentation/access-control.html)
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..5365edc
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,4 @@
+Configuration
+=============
+
+The @PLUGIN@ plugin does not require any additional configuration.
diff --git a/src/main/resources/Documentation/rest-api-accounts.md b/src/main/resources/Documentation/rest-api-accounts.md
new file mode 100644
index 0000000..97665fb
--- /dev/null
+++ b/src/main/resources/Documentation/rest-api-accounts.md
@@ -0,0 +1,69 @@
+@PLUGIN@ - /accounts/ REST API
+==============================
+
+This page describes the project related REST endpoints that are added
+by the @PLUGIN@.
+
+Please also take note of the general information on the
+[REST API](../../../Documentation/rest-api.html).
+
+<a id="project-endpoints"> Account Endpoints
+--------------------------------------------
+
+### <a id="delete-project"> Delete Account
+_DELETE /accounts/[\{account-id\}](../../../Documentation/rest-api-accounts.html#account-id)_
+
+OR
+
+_POST /accounts/[\{account-id\}](../../../Documentation/rest-api-accounts.html#account-id)/@PLUGIN@~delete_
+
+Deletes an account.
+
+Options for the deletion can be specified in the request body as a
+[DeleteOptionsInput](#delete-options-input) entity.
+
+Please note that some proxies prohibit request bodies for _DELETE_
+requests. In this case, if you want to specify options, use _POST_
+to delete the project.
+
+Caller must be a member of a group that is granted the 'Delete Account'
+capability (provided by this plugin), or granted the 'Delete Own Account' to
+be able to remove its own account.
+
+#### Request
+
+```
+  DELETE /accounts/1000002 HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  {
+    "account_name": "John Doe"
+  }
+```
+
+#### Response
+
+```
+  HTTP/1.1 204 No Content
+```
+
+
+<a id="json-entities">JSON Entities
+-----------------------------------
+
+### <a id="delete-options-info"></a>DeleteOptionsInfo
+
+The `DeleteOptionsInfo` entity contains options for the deletion of a
+project.
+
+* _account_name_ (optional): If set to the account full name, the account is deleted. Otherwise
+                             the invocation is only a dry-run that display the account details.
+
+SEE ALSO
+--------
+
+* [Accounts related REST endpoints](../../../Documentation/rest-api-accounts.html)
+
+GERRIT
+------
+Part of [Gerrit Code Review](../../../Documentation/index.html)
diff --git a/src/test/java/com/gerritforge/gerrit/plugins/account/test/DeleteAccountCommandTest.java b/src/test/java/com/gerritforge/gerrit/plugins/account/test/DeleteAccountCommandTest.java
new file mode 100644
index 0000000..f8fcb38
--- /dev/null
+++ b/src/test/java/com/gerritforge/gerrit/plugins/account/test/DeleteAccountCommandTest.java
@@ -0,0 +1,72 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account.test;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.gerritforge.gerrit.plugins.account.AccountPersonalInformation;
+import com.gerritforge.gerrit.plugins.account.AccountResourceFactory;
+import com.gerritforge.gerrit.plugins.account.DeleteAccount;
+import com.gerritforge.gerrit.plugins.account.DeleteAccountCommand;
+import com.gerritforge.gerrit.plugins.account.DeleteAccountResponse;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResource;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+import org.apache.sshd.server.Environment;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DeleteAccountCommandTest {
+
+  private DeleteAccountCommand deleteAccountCommand;
+
+  @Mock private AccountResourceFactory accountFactoryMock;
+  @Mock private DeleteAccount deleteAccountMock;
+  @Mock private Environment envMock;
+  @Mock private AccountResource accountResourceMock;
+  @Mock private IdentifiedUser userMock;
+
+  @Before
+  public void setup() throws Exception {
+    deleteAccountCommand = new DeleteAccountCommand(accountFactoryMock, deleteAccountMock);
+    deleteAccountCommand.setPrintWriters(
+        new PrintWriter(new ByteArrayOutputStream()), new PrintWriter(new ByteArrayOutputStream()));
+    DeleteAccountResponse resp =
+        new DeleteAccountResponse(true, new AccountPersonalInformation(userMock));
+    when(deleteAccountMock.apply(same(accountResourceMock), any(DeleteAccount.Input.class)))
+        .thenReturn(resp);
+  }
+
+  @Test
+  public void givenValidAccount_whenStart_shouldInvokeDeleteAccount() throws Exception {
+    int expectedAccountId = 1;
+    DeleteAccount.Input expectedInput = new DeleteAccount.Input();
+    expectedInput.accountName = "First Last";
+    when(accountFactoryMock.create(expectedAccountId)).thenReturn(accountResourceMock);
+
+    deleteAccountCommand.testRun(expectedAccountId, expectedInput.accountName);
+
+    verify(deleteAccountMock).apply(same(accountResourceMock), eq(expectedInput));
+  }
+}
diff --git a/src/test/java/com/gerritforge/gerrit/plugins/account/test/DeleteAccountTest.java b/src/test/java/com/gerritforge/gerrit/plugins/account/test/DeleteAccountTest.java
new file mode 100644
index 0000000..c5d6782
--- /dev/null
+++ b/src/test/java/com/gerritforge/gerrit/plugins/account/test/DeleteAccountTest.java
@@ -0,0 +1,91 @@
+// Copyright (C) 2018 GerritForge Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gerritforge.gerrit.plugins.account.test;
+
+import static org.mockito.Mockito.*;
+
+import com.gerritforge.gerrit.plugins.account.AccountRemover;
+import com.gerritforge.gerrit.plugins.account.DeleteAccount;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountResource;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DeleteAccountTest {
+
+  @Mock private AccountRemover accountRemoverMock;
+
+  @Mock private AccountResource accountResourceMock;
+
+  @Mock private IdentifiedUser userMock;
+
+  @Before
+  public void setup() {
+    when(accountResourceMock.getUser()).thenReturn(userMock);
+  }
+
+  @Test
+  public void givenAccountWithPermissions_whenRunningDeleteAccount_thenAccountRemoverIsInvoked()
+      throws Exception {
+    int accountId = 1;
+    DeleteAccount.Input input = new DeleteAccount.Input();
+    input.accountName = "First Last";
+    mockUserData(accountId, input.accountName);
+    when(accountRemoverMock.canDelete(accountId)).thenReturn(true);
+
+    new DeleteAccount(accountRemoverMock).apply(accountResourceMock, input);
+
+    verify(accountRemoverMock).removeAccount(accountId);
+  }
+
+  @Test(expected = AuthException.class)
+  public void givenAccountWithoutPermissions_whenRunningDeleteAccount_thenThrowAuthException()
+      throws Exception {
+    int accountId = 1;
+    DeleteAccount.Input input = new DeleteAccount.Input();
+    input.accountName = "First Last";
+    mockUserData(accountId, input.accountName);
+    when(accountRemoverMock.canDelete(accountId)).thenReturn(false);
+
+    new DeleteAccount(accountRemoverMock).apply(accountResourceMock, input);
+
+    verify(accountRemoverMock, times(0)).removeAccount(accountId);
+  }
+
+  @Test
+  public void givenAccountWithNonMatching_whenRunningDeleteAccount_thenAccountRemoverIsNotInvoked()
+      throws Exception {
+    int accountId = 1;
+    DeleteAccount.Input input = new DeleteAccount.Input();
+    input.accountName = "NonMatching Name";
+    mockUserData(accountId, "First Last");
+    when(accountRemoverMock.canDelete(accountId)).thenReturn(true);
+
+    new DeleteAccount(accountRemoverMock).apply(accountResourceMock, input);
+
+    verify(accountRemoverMock, times(0)).removeAccount(accountId);
+  }
+
+  private void mockUserData(int accountId, String accountName) {
+    when(userMock.getAccountId()).thenReturn(new Account.Id(accountId));
+    when(userMock.getName()).thenReturn(accountName);
+  }
+}
diff --git a/tools/bazel.rc b/tools/bazel.rc
new file mode 100644
index 0000000..4ed16cf
--- /dev/null
+++ b/tools/bazel.rc
@@ -0,0 +1,2 @@
+build --workspace_status_command=./tools/workspace-status.sh
+test --build_tests_only
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/bzl/BUILD
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl
new file mode 100644
index 0000000..dfcbe9c
--- /dev/null
+++ b/tools/bzl/classpath.bzl
@@ -0,0 +1,2 @@
+load("@com_googlesource_gerrit_bazlets//tools:classpath.bzl",
+     "classpath_collector")
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
new file mode 100644
index 0000000..3af7e58
--- /dev/null
+++ b/tools/bzl/junit.bzl
@@ -0,0 +1,4 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
+    "junit_tests",
+)
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
new file mode 100644
index 0000000..2eabedb
--- /dev/null
+++ b/tools/bzl/maven_jar.bzl
@@ -0,0 +1 @@
+load("@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl", "maven_jar")
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
new file mode 100644
index 0000000..a2e438f
--- /dev/null
+++ b/tools/bzl/plugin.bzl
@@ -0,0 +1,6 @@
+load(
+    "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
+    "gerrit_plugin",
+    "PLUGIN_DEPS",
+    "PLUGIN_TEST_DEPS",
+)
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
new file mode 100644
index 0000000..0c7503d
--- /dev/null
+++ b/tools/eclipse/BUILD
@@ -0,0 +1,9 @@
+load("//tools/bzl:classpath.bzl", "classpath_collector")
+
+classpath_collector(
+    name = "main_classpath_collect",
+    testonly = 1,
+    deps = [
+        "//:delete-project__plugin_test_deps",
+    ],
+)
diff --git a/tools/eclipse/project.sh b/tools/eclipse/project.sh
new file mode 100755
index 0000000..55713c7
--- /dev/null
+++ b/tools/eclipse/project.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+`bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project --output location | sed s/BUILD:.*//`project.py -n delete-project -r .
diff --git a/tools/workspace-status.sh b/tools/workspace-status.sh
new file mode 100755
index 0000000..3185cae
--- /dev/null
+++ b/tools/workspace-status.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# This script will be run by bazel when the build process starts to
+# generate key-value information that represents the status of the
+# workspace. The output should be like
+#
+# KEY1 VALUE1
+# KEY2 VALUE2
+#
+# If the script exits with non-zero code, it's considered as a failure
+# and the output will be discarded.
+
+function rev() {
+  cd $1; git describe --always --match "v[0-9].*" --dirty
+}
+
+echo STABLE_BUILD_DELETE-PROJECT_LABEL $(rev .)