Support expanding ${username} references to email address.

All users have an email address but may not have a username. This
allows users to create refs with any email address associated
with their account.

Change-Id: Ia17caf40652839f8ac1c7c2177b4579632c01973
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index 26840a1..3b6ce91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -27,6 +27,7 @@
 import com.google.inject.Singleton;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -59,22 +60,23 @@
      *        priority order (project specific definitions must appear before
      *        inherited ones).
      * @param ref reference being accessed.
-     * @param username if the reference is a per-user reference, access sections
-     *        using the parameter variable "${username}" will first have {@code
-     *        username} inserted into them before seeing if they apply to the
-     *        reference named by {@code ref}. If null, per-user references are
-     *        ignored.
+     * @param usernames if the reference is a per-user reference, access sections
+     *        using the parameter variable "${username}" will first have each of
+     *        {@code usernames} inserted into them before seeing if they apply to
+     *        the reference named by {@code ref}. If null or empty, per-user
+     *        references are ignored.
      * @return map of permissions that apply to this reference, keyed by
      *         permission name.
      */
     PermissionCollection filter(Iterable<SectionMatcher> matcherList,
-        String ref, String username) {
+        String ref, Collection<String> usernames) {
       if (isRE(ref)) {
         ref = RefControl.shortestExample(ref);
       } else if (ref.endsWith("/*")) {
         ref = ref.substring(0, ref.length() - 1);
       }
 
+      boolean hasUsernames = usernames != null && !usernames.isEmpty();
       boolean perUser = false;
       Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
       for (SectionMatcher sm : matcherList) {
@@ -90,12 +92,17 @@
         // that will never be shared with non-user references, and the per-user
         // references are usually less frequent than the non-user references.
         //
-        if (username != null && !perUser
-            && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
-          perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
-        }
-
-        if (sm.match(ref, username)) {
+        if (hasUsernames) {
+          if (!perUser && sm.matcher instanceof RefPatternMatcher.ExpandParameters) {
+            perUser = ((RefPatternMatcher.ExpandParameters) sm.matcher).matchPrefix(ref);
+          }
+          for (String username : usernames) {
+            if (sm.match(ref, username)) {
+              sectionToProject.put(sm.section, sm.project);
+              break;
+            }
+          }
+        } else if (sm.match(ref, null)) {
           sectionToProject.put(sm.section, sm.project);
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 30cfc2b..1156677 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
@@ -191,8 +192,15 @@
     }
     RefControl ctl = refControls.get(refName);
     if (ctl == null) {
+      ImmutableList.Builder<String> usernames = ImmutableList.<String> builder();
+      if (user.getUserName() != null) {
+        usernames.add(user.getUserName());
+      }
+      if (user instanceof IdentifiedUser) {
+        usernames.addAll(((IdentifiedUser) user).getEmailAddresses());
+      }
       PermissionCollection relevant =
-          permissionFilter.filter(access(), refName, user.getUserName());
+          permissionFilter.filter(access(), refName, usernames.build());
       ctl = new RefControl(this, refName, relevant);
       refControls.put(refName, ctl);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
index 0c8ecb8..dcf6841 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -105,7 +105,7 @@
 
       String u;
       if (isRE(template.getPattern())) {
-        u = username.replace(".", "\\.");
+        u = Pattern.quote(username);
       } else {
         u = username;
       }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index 64ce398..594580f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -247,6 +247,18 @@
   }
 
   @Test
+  public void testUsernameEmailPatternWithRegex() {
+    grant(local, READ, DEVS, "^refs/sb/${username}/heads/.*");
+
+    ProjectControl u = util.user(local, "d.v@ger-rit.org", DEVS);
+    ProjectControl d = util.user(local, "dev@ger-rit.org", DEVS);
+    assertFalse("u can't read",
+        u.controlForRef("refs/sb/dev@ger-rit.org/heads/foobar").isVisible());
+    assertTrue("d can read",
+        d.controlForRef("refs/sb/dev@ger-rit.org/heads/foobar").isVisible());
+  }
+
+  @Test
   public void testSortWithRegex() {
     grant(local, READ, DEVS, "^refs/heads/.*");
     grant(util.getParentConfig(), READ, ANONYMOUS_USERS, "^refs/heads/.*-QA-.*");