Support deletion of email address via REST

Change-Id: I3637a7f4497ddc2f88523728e5315ff56da92b8f
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index a78385a..075826b 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -267,6 +267,24 @@
   }
 ----
 
+[[delete-account-email]]
+Delete Account Email
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /accounts/link:#account-id[\{account-id\}]/emails/link:#email-id[\{email-id\}]'
+
+Deletes an email address of an account.
+
+.Request
+----
+  DELETE /accounts/self/emails/john.doe@example.com HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[set-preferred-email]]
 Set Preferred Email
 ~~~~~~~~~~~~~~~~~~~
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index c21d375..10b34ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -435,42 +435,38 @@
    *         cannot be unlinked at this time.
    */
   public AuthResult unlink(final Account.Id from, AuthRequest who)
-      throws AccountException {
+      throws AccountException, OrmException {
+    final ReviewDb db = schema.open();
     try {
-      final ReviewDb db = schema.open();
-      try {
-        who = realm.unlink(db, from, who);
+      who = realm.unlink(db, from, who);
 
-        final AccountExternalId.Key key = id(who);
-        AccountExternalId extId = db.accountExternalIds().get(key);
-        if (extId != null) {
-          if (!extId.getAccountId().equals(from)) {
-            throw new AccountException("Identity in use by another account");
+      final AccountExternalId.Key key = id(who);
+      AccountExternalId extId = db.accountExternalIds().get(key);
+      if (extId != null) {
+        if (!extId.getAccountId().equals(from)) {
+          throw new AccountException("Identity in use by another account");
+        }
+        db.accountExternalIds().delete(Collections.singleton(extId));
+
+        if (who.getEmailAddress() != null) {
+          final Account a = db.accounts().get(from);
+          if (a.getPreferredEmail() != null
+              && a.getPreferredEmail().equals(who.getEmailAddress())) {
+            a.setPreferredEmail(null);
+            db.accounts().update(Collections.singleton(a));
           }
-          db.accountExternalIds().delete(Collections.singleton(extId));
-
-          if (who.getEmailAddress() != null) {
-            final Account a = db.accounts().get(from);
-            if (a.getPreferredEmail() != null
-                && a.getPreferredEmail().equals(who.getEmailAddress())) {
-              a.setPreferredEmail(null);
-              db.accounts().update(Collections.singleton(a));
-            }
-            byEmailCache.evict(who.getEmailAddress());
-            byIdCache.evict(from);
-          }
-
-        } else {
-          throw new AccountException("Identity not found");
+          byEmailCache.evict(who.getEmailAddress());
+          byIdCache.evict(from);
         }
 
-        return new AuthResult(from, key, false);
-
-      } finally {
-        db.close();
+      } else {
+        throw new AccountException("Identity not found");
       }
-    } catch (OrmException e) {
-      throw new AccountException("Cannot unlink identity", e);
+
+      return new AuthResult(from, key, false);
+
+    } finally {
+      db.close();
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
new file mode 100644
index 0000000..67bb968
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -0,0 +1,75 @@
+// Copyright (C) 2013 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.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.reviewdb.client.Account.FieldName;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.DeleteEmail.Input;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> {
+  static class Input {
+  }
+
+  private final Provider<CurrentUser> self;
+  private final Realm realm;
+  private final Provider<ReviewDb> dbProvider;
+  private final AccountManager accountManager;
+
+  @Inject
+  DeleteEmail(Provider<CurrentUser> self, Realm realm,
+      Provider<ReviewDb> dbProvider, AccountManager accountManager) {
+    this.self = self;
+    this.realm = realm;
+    this.dbProvider = dbProvider;
+    this.accountManager = accountManager;
+  }
+
+  @Override
+  public Object apply(AccountResource.Email rsrc, Input input)
+      throws AuthException, ResourceNotFoundException,
+      ResourceConflictException, MethodNotAllowedException, OrmException {
+    if (self.get() != rsrc.getUser()
+        && !self.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("not allowed to delete email address");
+    }
+    if (!realm.allowsEdit(FieldName.REGISTER_NEW_EMAIL)) {
+      throw new MethodNotAllowedException("realm does not allow deleting emails");
+    }
+    AccountExternalId.Key key = new AccountExternalId.Key(
+        AccountExternalId.SCHEME_MAILTO, rsrc.getEmail());
+    AccountExternalId extId = dbProvider.get().accountExternalIds().get(key);
+    if (extId == null) {
+      throw new ResourceNotFoundException(rsrc.getEmail());
+    }
+    try {
+      accountManager.unlink(rsrc.getUser().getAccountId(),
+          AuthRequest.forEmail(rsrc.getEmail()));
+    } catch (AccountException e) {
+      throw new ResourceConflictException(e.getMessage());
+    }
+    return Response.none();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 860a96e..4215cf2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -40,6 +40,7 @@
     child(ACCOUNT_KIND, "emails").to(Emails.class);
     get(EMAIL_KIND).to(GetEmail.class);
     put(EMAIL_KIND).to(PutEmail.class);
+    delete(EMAIL_KIND).to(DeleteEmail.class);
     put(EMAIL_KIND, "preferred").to(PutPreferred.class);
     get(ACCOUNT_KIND, "avatar").to(GetAvatar.class);
     get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 4c23833..8d24757 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -260,6 +260,8 @@
       manager.unlink(id, AuthRequest.forEmail(mailAddress));
     } catch (AccountException ex) {
       throw die(ex.getMessage());
+    } catch (OrmException ex) {
+      throw die(ex.getMessage());
     }
   }