Support ${shardeduserid} in ref patterns
This makes it possible to assign for each user permissions for the own
user branch, e.g. in the All-Users project READ for Registered-Users
can be granted on refs/users/${shardeduserid}.
Change-Id: If7d0ba58743562a0706edfc7ef602c8add2249d5
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 771e323..995b155 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -220,11 +220,16 @@
thus `^refs/heads/.*/name` will fail because `refs/heads//name`
is not a valid reference, but `^refs/heads/.+/name` will work.
-References can have the current user name automatically included,
-creating dynamic access controls that change to match the currently
-logged in user. For example to provide a personal sandbox space
-to all developers, `+refs/heads/sandbox/${username}/*+` allowing
-the user 'joe' to use 'refs/heads/sandbox/joe/foo'.
+References can have the user name or the sharded account ID of the
+current user automatically included, creating dynamic access controls
+that change to match the currently logged in user. For example to
+provide a personal sandbox space to all developers,
+`+refs/heads/sandbox/${username}/*+` allows the user 'joe' to use
+'refs/heads/sandbox/joe/foo'. The sharded account ID can be used to
+give users access to their user branch in the `All-Users` repository,
+for example `+refs/users/${shardeduserid}+` is resolved to
+'refs/users/23/1011123' if the account ID of the current user is
+`1011123`.
When evaluating a reference-level access right, Gerrit will use
the full set of access rights to determine if the user
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index adbc9ce..b2d6a90 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -61,6 +61,7 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.util.MagicBranch;
import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.FakeEmailSender.Message;
@@ -393,7 +394,8 @@
}
// allow each user to read its own user branch
- grant(Permission.READ, allUsers, RefNames.REFS_USERS + "*", false,
+ grant(Permission.READ, allUsers,
+ RefNames.REFS_USERS + "${" + RefPattern.USERID_SHARDED + "}", false,
REGISTERED_USERS);
// fetch user branch using refs/users/YY/XXXXXXX
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index ec4ddb2..1af82da 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -84,14 +84,7 @@
public static String changeMetaRef(Change.Id id) {
StringBuilder r = new StringBuilder();
r.append(REFS_CHANGES);
- int n = id.get();
- int m = n % 100;
- if (m < 10) {
- r.append('0');
- }
- r.append(m);
- r.append('/');
- r.append(n);
+ r.append(shard(id.get()));
r.append(META_SUFFIX);
return r.toString();
}
@@ -99,14 +92,7 @@
public static String refsUsers(Account.Id accountId) {
StringBuilder r = new StringBuilder();
r.append(REFS_USERS);
- int account = accountId.get();
- int m = account % 100;
- if (m < 10) {
- r.append('0');
- }
- r.append(m);
- r.append('/');
- r.append(account);
+ r.append(shard(accountId.get()));
return r.toString();
}
@@ -135,6 +121,16 @@
private static StringBuilder buildRefsPrefix(String prefix, int id) {
StringBuilder r = new StringBuilder();
r.append(prefix);
+ r.append(shard(id));
+ r.append('/');
+ return r;
+ }
+
+ public static String shard(int id) {
+ if (id < 0) {
+ return null;
+ }
+ StringBuilder r = new StringBuilder();
int n = id % 100;
if (n < 10) {
r.append('0');
@@ -142,8 +138,7 @@
r.append(n);
r.append('/');
r.append(id);
- r.append('/');
- return r;
+ return r.toString();
}
/**
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
index 4e3b659..af1c074 100644
--- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
@@ -141,4 +141,14 @@
assertThat(parseRefSuffix("a4")).isNull();
assertThat(parseRefSuffix("4a")).isNull();
}
+
+ @Test
+ public void shard() throws Exception {
+ assertThat(RefNames.shard(1011123)).isEqualTo("23/1011123");
+ assertThat(RefNames.shard(537)).isEqualTo("37/537");
+ assertThat(RefNames.shard(12)).isEqualTo("12/12");
+ assertThat(RefNames.shard(0)).isEqualTo("00/0");
+ assertThat(RefNames.shard(1)).isEqualTo("01/1");
+ assertThat(RefNames.shard(-1)).isNull();
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPattern.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPattern.java
index 2b03951..52663d0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPattern.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefPattern.java
@@ -26,6 +26,7 @@
import java.util.regex.PatternSyntaxException;
public class RefPattern {
+ public static final String USERID_SHARDED = "shardeduserid";
public static final String USERNAME = "username";
public static String shortestExample(String refPattern) {
@@ -77,7 +78,9 @@
public static void validateRegExp(String refPattern)
throws InvalidNameException {
try {
- Pattern.compile(refPattern.replace("${" + USERNAME + "}/", ""));
+ refPattern = refPattern.replace("${" + USERID_SHARDED + "}", "");
+ refPattern = refPattern.replace("${" + USERNAME + "}", "");
+ Pattern.compile(refPattern);
} catch (PatternSyntaxException e) {
throw new InvalidNameException(refPattern + " " + e.getMessage());
}
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 97f70b4..d8f4054 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
@@ -16,14 +16,18 @@
import static com.google.gerrit.server.project.RefPattern.isRE;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import dk.brics.automaton.Automaton;
-import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@@ -89,15 +93,17 @@
template = new ParameterizedString(pattern);
if (isRE(pattern)) {
- // Replace ${username} with ":USERNAME:" as : is not legal
- // in a reference and the string :USERNAME: is not likely to
- // be a valid part of the regex. This later allows the pattern
- // prefix to be clipped, saving time on evaluation.
- String replacement = ":" + RefPattern.USERNAME.toUpperCase() + ":";
+ // Replace ${username} and ${shardeduserid} with ":PLACEHOLDER:"
+ // as : is not legal in a reference and the string :PLACEHOLDER:
+ // is not likely to be a valid part of the regex. This later
+ // allows the pattern prefix to be clipped, saving time on
+ // evaluation.
+ String replacement = ":PLACEHOLDER:";
+ Map<String, String> params = ImmutableMap.of(
+ RefPattern.USERID_SHARDED, replacement,
+ RefPattern.USERNAME, replacement);
Automaton am =
- RefPattern.toRegExp(
- template.replace(Collections.singletonMap(RefPattern.USERNAME,
- replacement))).toAutomaton();
+ RefPattern.toRegExp(template.replace(params)).toAutomaton();
String rePrefix = am.getCommonPrefix();
prefix = rePrefix.substring(0, rePrefix.indexOf(replacement));
} else {
@@ -119,8 +125,11 @@
u = username;
}
- RefPatternMatcher next = getMatcher(expand(template, u));
- if (next != null && next.match(expand(ref, u), user)) {
+ Account.Id accountId = user.isIdentifiedUser()
+ ? user.getAccountId()
+ : null;
+ RefPatternMatcher next = getMatcher(expand(template, u, accountId));
+ if (next != null && next.match(expand(ref, u, accountId), user)) {
return true;
}
}
@@ -147,16 +156,21 @@
return ref.startsWith(prefix);
}
- private String expand(String parameterizedRef, String userName) {
+ private String expand(String parameterizedRef, String userName, Account.Id accountId) {
if (parameterizedRef.contains("${")) {
- return expand(new ParameterizedString(parameterizedRef), userName);
+ return expand(new ParameterizedString(parameterizedRef), userName, accountId);
}
return parameterizedRef;
}
- private String expand(ParameterizedString parameterizedRef, String userName) {
- return parameterizedRef
- .replace(Collections.singletonMap(RefPattern.USERNAME, userName));
+ private String expand(ParameterizedString parameterizedRef, String userName,
+ Account.Id accountId) {
+ Map<String, String> params = new HashMap<>();
+ params.put(RefPattern.USERNAME, userName);
+ if (accountId != null) {
+ params.put(RefPattern.USERID_SHARDED, RefNames.shard(accountId.get()));
+ }
+ return parameterizedRef.replace(params);
}
}
}