Merge branch 'stable-3.3' into master

* stable-3.3:
  Update JGit to 9f3616dcb
  Make PermissionBackend#ForRef authoritative
  Revert "Upgrade metrics-core to 4.1.14"
  Update git submodules
  Introduce cache.openFiles setting
  Update jgit to e2663a8b8
  Set version to 3.2.5.1
  Revert "Add latency tracking for porting comments"
  Disk cache metrics require cache.enableDiskStatMetrics
  Set version to 2.14.22
  Fix Shift-A shortcut for hiding the left side of the diff
  ElasticContainer: Upgrade V6_8 to elasticsearch 6.8.13
  Upgrade testcontainers to 1.15.0
  Update git submodules
  Workaround Gitiles bug on All-Users visibility
  Validate Gerrit changes on stable-2.15 with Jenkins
  Set version to 3.3.0-SNAPSHOT
  Set version to 3.3.0-rc6
  Set version to 2.15.22-SNAPSHOT
  Set version to 3.0.16-SNAPSHOT
  Set version to 2.15.21
  Set version to 3.0.15
  Set version to 2.16.26-SNAPSHOT
  Set version to 2.16.25
  Clean up comment experiment flags
  Set version to 3.1.11-SNAPSHOT
  Set version to 3.1.10
  Workaround Gitiles bug on All-Users visibility
  Workaround Gitiles bug on All-Users visibility
  Workaround Gitiles bug on All-Users visibility
  Workaround Gitiles bug on All-Users visibility
  Set version to 3.2.6-SNAPSHOT
  Set version to 3.2.5
  Workaround Gitiles bug on All-Users visibility
  Set version to 2.15.21-SNAPSHOT
  Set version to 2.15.20
  Fetch JGit documentation from the archive site
  Remove generation for c.g.gwtexpui.* JavaDoc
  Set version to 3.2.5-SNAPSHOT
  Set version to 3.2.4
  Set version to 3.1.10-SNAPSHOT
  Set version to 3.1.9
  Convert comment counts to comment thread counts around the UI
  Set version to 3.0.15-SNAPSHOT
  Set version to 2.16.25-SNAPSHOT
  Set version to 3.0.14
  Set version to 2.16.24
  Fix label votes not showing up when not logged in
  Fix showing "Add Patchset Description" for users that are not logged in
  Make PermissionBackend#ForRef authoritative
  Validate Gerrit changes on stable-2.15 with Jenkins
  Fix tests for stable-2.15 branch
  Make PermissionBackend#ForRef authoritative
  Make PermissionBackend#ForRef authoritative
  Make PermissionBackend#ForRef authoritative
  Specify line-height for code in terms of rem
  Fix iron-dropdown positioning
  Provide clearer above / below mapping for context expansion buttons
  Refuse to expand skip groups with WHOLE_FILE
  Abort cursor moves via a sentinel, not a callback
  Don't unset scroll on non-existant stop
  Make tests independent of gr-diff rendering
  Allow multiple plugins to provide coverage data
  Fix gr-main-header
  Rename gr-main-header_test.js to gr-main-header_test.ts
  Add support to skip chunks
  Avoid sharing search operators between gr-search-bar instances
  Use 'current' for latest patchnum when it's not set
  Only request ported drafts if user is logged in
  Catch errors in getPortedComments request
  Hide label row if user cannot vote on it
  Remove committer info if it is the same as the uploader
  Add latency tracking for porting comments
  Add Typescript test migration instruction to README.md
  Update revision type in FetchChangeJSON
  Add hasDrafts property to re-compute attention set
  Remove non-edit top level actions in edit mode
  Convert gr-change-view tests to typescript
  Rename gr-change-view_test.js to gr-change-view_test.ts
  Remove generateChange method and instead use test-data-generators.ts
  Add test-data-generators and convert several test files to TS
  Add Publish button to editor
  Update Save button to Save & Close file in edit view
  A11y - Fix reading whole diff preferences dialog
  Cleanup eslint rules and fix some eslint warnings
  Allow to write and run Typescript tests
  Fix headline for user queries for users without name
  A11y - headlines for Diff Page
  A11y - headlines for Change Page
  Pass change info object to coverage annotation provider.
  A11y - headlines for Dashboard Page
  Assign correct type to patchNum in tests
  Do not convert PatchSetNum type to string
  Check detail type in gr-repo-detail
  Replace polymer readonly attribute with ts readonly
  A11y - Remove <hr> from headlines
  Sort labels in change-view similar to reply dialog
  Linkify branch name in repo detail list
  Typescript 4.0 fixes
  A11y - Add meaningful label for Delete Comment button
  Do not navigate to Base vs X if X is Base and file is unchanged
  Fix types in common.ts
  Unit test for Fix navigate back to change page with '[' on first diff
  Clean up patchset conversion in change view params
  Convert patch set from url to PatchSetNum type
  Add keyboard shortcut 'f' to open file list in diff view
  Fix navigate back to change page with '[' on first diff.
  Update mixins to avoid compilation issues
  Required fixes to match internal google rules
  Convert embed/gr-diff and gr-diff-root to ts
  Ensure that all assignment to window.Gerrit properties are safe
  Convert files to typescript
  Rename files to preserve history
  Remove duplication of types
  Tests: fix 500 response when requesting gr-diff-view.ts
  Show comment in viewport when navigating to Base vs X
  Add tests for moveToNextChunk in case of dueToMove=true
  Do not skip lines marked as dueToMove
  Replace parseInt() with Number
  Fix TseTse errors
  Fix repositioning the reply dialog when the attention section expands
  Allow to remove reviewers that don't have _account_id
  Convert gr-change-view to typescript
  Rename files to preserve history
  Improve tests infrastructures
  Convert <gr-diff-view> to typescript
  Rename files to preserve history
  Fix crash when gr-diff-host is reloading.
  Add event detail interfaces for dispatchEventThroughTarget
  Replace "Since" by "Waiting"
  Fix patchset comparison when adding coverages
  Convert files to typescript
  Fix argument type for dispatchEventThroughTarget
  Rename files to preserve history
  Convert files to typescript
  Rename files to preserve history
  Convert gr-smart-search to typescript
  Change the tooltip of the 'MODIFY' button to be fancier
  Fix TseTse errors.
  Fix 'Send' button being enabled even without the user making any changes
  Toggle default to show unresolved comments
  Do not add reviewer to the attention set when they add themselves
  Fix attention set tip not showing up
  Make gr-diff own "loading" state
  Rename files to preserve history
  Convert files to typescript
  Rename files to preserve history
  Convert files to typescript
  Rename files to preserve history
  Convert files to typescript
  Add more functionalities to hovercard
  Add email notification option ATTENTION_SET_ONLY
  Fix filter function type in reply dialog
  Rename files to preserve history
  Convert files to typescript
  Rename files to preserve history
  Convert files to typescript
  Replace dispatchEvent with dispatchEventThroughTarget
  Use await flush() instead of setTimeout in tests
  Fix TseTse error
  Convert patchNum into integer if possible
  Rename files to preserve history
  Fix js error when `all` is not set in LabelInfo
  Refactor reload 9: Extract promisification
  Refactor reload 8: Extract getting layers
  Refactor reload 7: Await syntax highlighting
  Refactor reload 6: Merge try blocks
  Refactor reload 5: Await _getDiff()
  Refactor reload 4: Separate await diff and asset
  Refactor reload 3: Await diff and asset request
  Refactor reload 2: Await render promise
  Refactor reload 1: Promisify callback
  Convert files to typescript
  Rename files to preserve history
  Convert files to typescript
  Rename files to preserve history
  Change when and how attention can be modified in the reply dialog
  Use the GrDiff type now that it's available
  Use Side type for checking if line is left or not
  Use a selection chip also in the attention summary of the reply dialog
  Bold the user name, if they are in the attention set
  Do not show attention related hovercard actions to guest users
  Fix showing the CC section in the attention modify section
  Change attention set logic to take unresolved state into account
  Do not add the owner to an otherwise empty attention set
  Add an attention-set-util
  Fix "Do not update attention set" message
  When adding more than 2 users to the attention set show a warning
  Add 'Since' and 'Submitted' columns to dashboard sections
  Change the sorting of changes in the 'Your Turn' dashboard section
  Highlight the dashboard rows with attention as reviewer
  Fix TypeScript compile error for JSON.parse() call
  Remove unused projectName
  Improve description of auth.skipFullRefEvaluationIfAllRefsAreVisible
  Verify hostname when sending emails via SMTP server with SMTPSClient
  Hide patch file download when the commit has multiple parents
  Make PermissionBackend#ForRef authoritative
  Docs: Clarify that 'm' push option sets patch set description

Change-Id: I207e72b690541a850857fed1a6d6c182e226f07a
This commit is contained in:
Marco Miller
2020-11-23 12:24:07 -05:00
28 changed files with 1243 additions and 237 deletions

View File

@@ -869,6 +869,11 @@ The change owner and any explicitly added reviewers can always see
private changes (even without having the `View Private Changes` access
right assigned).
**NOTE**: If link:config-gerrit.html#auth.skipFullRefEvaluationIfAllRefsAreVisible[
auth.skipFullRefEvaluationIfAllRefsAreVisible] is `true` (which is the case by
default) privates changes and all change edit refs are also visible to users
that have read access on `refs/*`.
[[category_toggle_work_in_progress_state]]
=== Toggle Work In Progress state

View File

@@ -89,7 +89,7 @@ evolves, such as "Added more unit tests." Unlike the change description, a patch
set description does not become a part of the project's history.
To add a patch set description, click *Add a patch set description*, located in
the file list.
the file list, or provide it link:user-upload.html#patch_set_description[on upload].
GERRIT
------

View File

@@ -684,8 +684,11 @@ By default, false.
[[auth.skipFullRefEvaluationIfAllRefsAreVisible]]auth.skipFullRefEvaluationIfAllRefsAreVisible::
+
Whether to skip the full ref visibility checks as a performance shortcut when all refs are
visible to a user. Full ref filtering would filter out things like pending edits.
Whether to skip the full ref visibility checks as a performance shortcut when a
user has READ permission for all refs.
+
The full ref filtering would filter out refs for pending edits, private changes
and auto merge commits.
+
By default, true.
@@ -742,6 +745,24 @@ for detail.
+
Default is false.
[[cache.openFiles]]cache.openFiles::
+
The number of file descriptors to add to the limit set by the Gerrit daemon.
+
Persistent caches are stored on the file system and as such participate in the
file descriptors utilization. The number of file descriptors can vary depending
on the cache configuration and the specific backend used.
+
The additional file descriptors required by the cache should be accounted for
via this setting, so that the Gerrit daemon can adjust the ulimit accordingly.
+
If you increase this to a larger setting you may need to also adjust
the ulimit on file descriptors for the host JVM, as Gerrit needs
additional file descriptors available for network sockets and other
repository data manipulation.
+
Default is 0.
[[cache.name.maxAge]]cache.<name>.maxAge::
+
Maximum age to keep an entry in the cache. Entries are removed from

View File

@@ -66,6 +66,11 @@ objects needing finalization.
* `caches/disk_hit_ratio`: Disk hit ratio for persistent cache.
* `caches/refresh_count`: The number of refreshes per cache with an indicator if a reload was necessary.
Cache disk metrics are expensive to compute on larger installations and are not
computed by default. They can be enabled via the
link:config.gerrit.html#cache.enableDiskStatMetrics[`cache.enableDiskStatMetrics`]
setting.
=== Change
* `change/submit_rule_evaluation`: Latency for evaluating submit rules on a change.

View File

@@ -315,11 +315,11 @@ link:intro-user.html#work-in-progress-by-default[user preference]. If the
preference is set so the default behavior is to create `work-in-progress`
changes, this can be overridden with the `ready` option.
[[message]]
==== Message
[[patch_set_description]]
==== Patch Set Description
A comment message can be applied to the change by using the `message` (or `m`)
option:
A link:concept-patch-sets.html#_description[patch set description] can be
applied by using the `message` (or `m`) option:
----
git push ssh://john.doe@git.example.com:29418/kernel/common HEAD:refs/for/experimental%m=This_is_a_rebase_on_master%21

View File

@@ -288,10 +288,16 @@ public class RefNames {
* Whether the ref is managed by Gerrit. Covers all Gerrit-internal refs like refs/cache-automerge
* and refs/meta as well as refs/changes. Does not cover user-created refs like branches or custom
* ref namespaces like refs/my-company.
*
* <p>Any ref for which this method evaluates to true will be served to users who have the {@code
* ACCESS_DATABASE} capability.
*
* <p><b>Caution</b>Any ref not in this list will be served if the user was granted a READ
* permission on it using Gerrit's permission model.
*/
public static boolean isGerritRef(String ref) {
return ref.startsWith(REFS_CHANGES)
|| ref.startsWith(REFS_META)
|| ref.startsWith(REFS_EXTERNAL_IDS)
|| ref.startsWith(REFS_CACHE_AUTOMERGE)
|| ref.startsWith(REFS_DRAFT_COMMENTS)
|| ref.startsWith(REFS_DELETED_GROUPS)
@@ -299,7 +305,8 @@ public class RefNames {
|| ref.startsWith(REFS_GROUPS)
|| ref.startsWith(REFS_GROUPNAMES)
|| ref.startsWith(REFS_USERS)
|| ref.startsWith(REFS_STARRED_CHANGES);
|| ref.startsWith(REFS_STARRED_CHANGES)
|| ref.startsWith(REFS_REJECT_COMMITS);
}
static Integer parseShardedRefPart(String name) {

View File

@@ -391,11 +391,7 @@ public class SmtpEmailSender implements EmailSender {
}
private SMTPClient open() throws EmailException {
final AuthSMTPClient client = new AuthSMTPClient(UTF_8.name());
if (smtpEncryption == Encryption.SSL) {
client.enableSSL(sslVerify);
}
final AuthSMTPClient client = new AuthSMTPClient(smtpEncryption == Encryption.SSL, sslVerify);
client.setConnectTimeout(connectTimeout);
try {
@@ -411,7 +407,7 @@ public class SmtpEmailSender implements EmailSender {
}
if (smtpEncryption == Encryption.TLS) {
if (!client.startTLS(smtpHost, smtpPort, sslVerify)) {
if (!client.execTLS()) {
throw new EmailException("SMTP server does not support TLS");
}
if (!client.login()) {

View File

@@ -61,11 +61,12 @@ class ChangeControl {
}
/** Can this user see this change? */
private boolean isVisible(ChangeData cd) {
if (getChange().isPrivate() && !isPrivateVisible(cd)) {
boolean isVisible() {
if (getChange().isPrivate() && !isPrivateVisible(changeData)) {
return false;
}
return refControl.isVisible();
// Does the user have READ permission on the destination?
return refControl.asForRef().testOrFalse(RefPermission.READ);
}
/** Can this user abandon this change? */
@@ -201,17 +202,13 @@ class ChangeControl {
private ForChangeImpl() {}
private ChangeData changeData() {
return changeData;
}
@Override
public String resourcePath() {
if (resourcePath == null) {
resourcePath =
String.format(
"/projects/%s/+changes/%s",
getProjectControl().getProjectState().getName(), changeData().getId().get());
getProjectControl().getProjectState().getName(), changeData.getId().get());
}
return resourcePath;
}
@@ -256,7 +253,7 @@ class ChangeControl {
try {
switch (perm) {
case READ:
return isVisible(changeData());
return isVisible();
case ABANDON:
return canAbandon();
case DELETE:

View File

@@ -17,10 +17,7 @@ package com.google.gerrit.server.permissions;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.flogger.LazyArgs.lazy;
import static com.google.gerrit.entities.RefNames.REFS_CACHE_AUTOMERGE;
import static com.google.gerrit.entities.RefNames.REFS_CONFIG;
import static com.google.gerrit.entities.RefNames.REFS_USERS_SELF;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toCollection;
import com.google.auto.value.AutoValue;
@@ -30,8 +27,6 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Project;
@@ -42,13 +37,10 @@ import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.TagMatcher;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -80,8 +72,8 @@ class DefaultRefFilter {
private final TagCache tagCache;
private final ChangeNotes.Factory changeNotesFactory;
@Nullable private final SearchingChangeCacheImpl changeCache;
private final GroupCache groupCache;
private final PermissionBackend permissionBackend;
private final RefVisibilityControl refVisibilityControl;
private final ProjectControl projectControl;
private final CurrentUser user;
private final ProjectState projectState;
@@ -97,16 +89,16 @@ class DefaultRefFilter {
TagCache tagCache,
ChangeNotes.Factory changeNotesFactory,
@Nullable SearchingChangeCacheImpl changeCache,
GroupCache groupCache,
PermissionBackend permissionBackend,
RefVisibilityControl refVisibilityControl,
@GerritServerConfig Config config,
MetricMaker metricMaker,
@Assisted ProjectControl projectControl) {
this.tagCache = tagCache;
this.changeNotesFactory = changeNotesFactory;
this.changeCache = changeCache;
this.groupCache = groupCache;
this.permissionBackend = permissionBackend;
this.refVisibilityControl = refVisibilityControl;
this.skipFullRefEvaluationIfAllRefsAreVisible =
config.getBoolean("auth", "skipFullRefEvaluationIfAllRefsAreVisible", true);
this.projectControl = projectControl;
@@ -227,131 +219,56 @@ class DefaultRefFilter {
logger.atFinest().log("Doing full ref filtering");
fullFilterCount.increment();
boolean viewMetadata;
boolean isAdmin;
Account.Id userId;
IdentifiedUser identifiedUser;
PermissionBackend.WithUser withUser = permissionBackend.user(user);
if (user.isIdentifiedUser()) {
viewMetadata = withUser.testOrFalse(GlobalPermission.ACCESS_DATABASE);
isAdmin = withUser.testOrFalse(GlobalPermission.ADMINISTRATE_SERVER);
identifiedUser = user.asIdentifiedUser();
userId = identifiedUser.getAccountId();
logger.atFinest().log(
"Account = %d; can view metadata = %s; is admin = %s",
userId.get(), viewMetadata, isAdmin);
} else {
logger.atFinest().log("User is anonymous");
viewMetadata = false;
isAdmin = false;
userId = null;
identifiedUser = null;
}
boolean hasAccessDatabase =
permissionBackend
.user(projectControl.getUser())
.testOrFalse(GlobalPermission.ACCESS_DATABASE);
List<Ref> resultRefs = new ArrayList<>(refs.size());
List<Ref> deferredTags = new ArrayList<>();
for (Ref ref : refs) {
String name = ref.getName();
String refName = ref.getName();
Change.Id changeId;
Account.Id accountId;
AccountGroup.UUID accountGroupUuid;
if (name.startsWith(REFS_CACHE_AUTOMERGE)) {
continue;
} else if (opts.filterMeta() && isMetadata(name)) {
logger.atFinest().log("Filter out metadata ref %s", name);
continue;
} else if (RefNames.isRefsEdit(name)) {
// Edits are visible only to the owning user, if change is visible.
if (viewMetadata || visibleEdit(repo, name)) {
logger.atFinest().log("Include edit ref %s", name);
resultRefs.add(ref);
} else {
logger.atFinest().log("Filter out edit ref %s", name);
}
} else if ((changeId = Change.Id.fromRef(name)) != null) {
// Change ref is visible only if the change is visible.
if (viewMetadata || visible(repo, changeId)) {
logger.atFinest().log("Include change ref %s", name);
resultRefs.add(ref);
} else {
logger.atFinest().log("Filter out change ref %s", name);
}
} else if ((accountId = Account.Id.fromRef(name)) != null) {
// Account ref is visible only to the corresponding account.
if (viewMetadata || (accountId.equals(userId) && canReadRef(name))) {
logger.atFinest().log("Include user ref %s", name);
resultRefs.add(ref);
} else {
logger.atFinest().log("Filter out user ref %s", name);
}
} else if ((accountGroupUuid = AccountGroup.UUID.fromRef(name)) != null) {
// Group ref is visible only to the corresponding owner group.
InternalGroup group = groupCache.get(accountGroupUuid).orElse(null);
if (viewMetadata
|| (group != null
&& isGroupOwner(group, identifiedUser, isAdmin)
&& canReadRef(name))) {
logger.atFinest().log("Include group ref %s", name);
resultRefs.add(ref);
} else {
logger.atFinest().log("Filter out group ref %s", name);
}
if (opts.filterMeta() && isMetadata(refName)) {
logger.atFinest().log("Filter out metadata ref %s", refName);
} else if (isTag(ref)) {
if (hasReadOnRefsStar) {
// The user has READ on refs/*. This is the broadest permission one can assign. There is
// no way to grant access to (specific) tags in Gerrit, so we have to assume that these
// users can see all tags because there could be tags that aren't reachable by any visible
// ref while the user can see all non-Gerrit refs. This matches Gerrit's historic
// behavior.
// The user has READ on refs/* with no effective block permission. This is the broadest
// permission one can assign. There is no way to grant access to (specific) tags in
// Gerrit,
// so we have to assume that these users can see all tags because there could be tags that
// aren't reachable by any visible ref while the user can see all non-Gerrit refs. This
// matches Gerrit's historic behavior.
// This makes it so that these users could see commits that they can't see otherwise
// (e.g. a private change ref) if a tag was attached to it. Tags are meant to be used on
// the regular Git tree that users interact with, not on any of the Gerrit trees, so this
// is a negligible risk.
logger.atFinest().log("Include tag ref %s because user has read on refs/*", name);
logger.atFinest().log("Include tag ref %s because user has read on refs/*", refName);
resultRefs.add(ref);
} else {
// If its a tag, consider it later.
if (ref.getObjectId() != null) {
logger.atFinest().log("Defer tag ref %s", name);
logger.atFinest().log("Defer tag ref %s", refName);
deferredTags.add(ref);
} else {
logger.atFinest().log("Filter out tag ref %s that is not a tag", name);
logger.atFinest().log("Filter out tag ref %s that is not a tag", refName);
}
}
} else if (name.startsWith(RefNames.REFS_SEQUENCES)) {
// Sequences are internal database implementation details.
if (viewMetadata) {
logger.atFinest().log("Include sequence ref %s", name);
} else if ((changeId = Change.Id.fromRef(refName)) != null) {
// This is a mere performance optimization. RefVisibilityControl could determine the
// visibility of these refs just fine. But instead, we use highly-optimized logic that
// looks only on the last 10k most recent changes using the change index and a cache.
if (hasAccessDatabase) {
resultRefs.add(ref);
} else if (!visible(repo, changeId)) {
logger.atFinest().log("Filter out invisible change ref %s", refName);
} else if (RefNames.isRefsEdit(refName) && !visibleEdit(repo, refName)) {
logger.atFinest().log("Filter out invisible change edit ref %s", refName);
} else {
logger.atFinest().log("Filter out sequence ref %s", name);
}
} else if (projectState.isAllUsers()
&& (name.equals(RefNames.REFS_EXTERNAL_IDS) || name.equals(RefNames.REFS_GROUPNAMES))) {
// The notes branches with the external IDs / group names must not be exposed to normal
// users.
if (viewMetadata) {
logger.atFinest().log("Include external IDs branch %s", name);
// Change is visible
resultRefs.add(ref);
} else {
logger.atFinest().log("Filter out external IDs branch %s", name);
}
} else if (canReadRef(ref.getLeaf().getName())) {
// Use the leaf to lookup the control data. If the reference is
// symbolic we want the control around the final target. If its
// not symbolic then getLeaf() is a no-op returning ref itself.
logger.atFinest().log(
"Include ref %s because its leaf %s is readable", name, ref.getLeaf().getName());
} else if (refVisibilityControl.isVisible(projectControl, ref.getLeaf().getName())) {
resultRefs.add(ref);
} else if (isRefsUsersSelf(ref)) {
// viewMetadata allows to see all account refs, hence refs/users/self should be included as
// well
if (viewMetadata) {
logger.atFinest().log("Include ref %s", REFS_USERS_SELF);
resultRefs.add(ref);
}
} else {
logger.atFinest().log("Filter out ref %s", name);
}
}
Result result = new AutoValue_DefaultRefFilter_Result(resultRefs, deferredTags);
@@ -374,7 +291,8 @@ class DefaultRefFilter {
r ->
!RefNames.isGerritRef(r.getName())
&& !r.getName().startsWith(RefNames.REFS_TAGS)
&& !r.isSymbolic())
&& !r.isSymbolic()
&& !r.getName().equals(RefNames.REFS_CONFIG))
// Don't use the default Java Collections.toList() as that is not size-aware and would
// expand an array list as new elements are added. Instead, provide a list that has the
// right size. This spares incremental list expansion which is quadratic in complexity.
@@ -520,10 +438,6 @@ class DefaultRefFilter {
return ref.getLeaf().getName().startsWith(Constants.R_TAGS);
}
private static boolean isRefsUsersSelf(Ref ref) {
return ref.getName().startsWith(REFS_USERS_SELF);
}
private boolean canReadRef(String ref) throws PermissionBackendException {
try {
permissionBackendForProject.ref(ref).check(RefPermission.READ);
@@ -544,17 +458,6 @@ class DefaultRefFilter {
return true;
}
private boolean isGroupOwner(
InternalGroup group, @Nullable IdentifiedUser user, boolean isAdmin) {
requireNonNull(group);
// Keep this logic in sync with GroupControl#isOwner().
boolean isGroupOwner =
isAdmin || (user != null && user.getEffectiveGroups().contains(group.getOwnerGroupUUID()));
logger.atFinest().log("User is owner of group %s = %s", group.getGroupUUID(), isGroupOwner);
return isGroupOwner;
}
/**
* Returns true if the user can see the provided change ref. Uses NoteDb for evaluation, hence
* does not suffer from the limitations documented in {@link SearchingChangeCacheImpl}.

View File

@@ -36,8 +36,10 @@ import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.GitReceivePackGroups;
import com.google.gerrit.server.config.GitUploadPackGroups;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
@@ -68,11 +70,14 @@ class ProjectControl {
private final Set<AccountGroup.UUID> uploadGroups;
private final Set<AccountGroup.UUID> receiveGroups;
private final PermissionBackend permissionBackend;
private final RefVisibilityControl refVisibilityControl;
private final GitRepositoryManager repositoryManager;
private final CurrentUser user;
private final ProjectState state;
private final PermissionCollection.Factory permissionFilter;
private final DefaultRefFilter.Factory refFilterFactory;
private final ChangeData.Factory changeDataFactory;
private final AllUsersName allUsersName;
private List<SectionMatcher> allSections;
private Map<String, RefControl> refControls;
@@ -84,16 +89,22 @@ class ProjectControl {
@GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
PermissionCollection.Factory permissionFilter,
PermissionBackend permissionBackend,
RefVisibilityControl refVisibilityControl,
GitRepositoryManager repositoryManager,
DefaultRefFilter.Factory refFilterFactory,
ChangeData.Factory changeDataFactory,
AllUsersName allUsersName,
@Assisted CurrentUser who,
@Assisted ProjectState ps) {
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.permissionFilter = permissionFilter;
this.permissionBackend = permissionBackend;
this.refVisibilityControl = refVisibilityControl;
this.repositoryManager = repositoryManager;
this.refFilterFactory = refFilterFactory;
this.changeDataFactory = changeDataFactory;
this.allUsersName = allUsersName;
user = who;
state = ps;
}
@@ -117,7 +128,9 @@ class ProjectControl {
RefControl ctl = refControls.get(refName);
if (ctl == null) {
PermissionCollection relevant = permissionFilter.filter(access(), refName, user);
ctl = new RefControl(changeDataFactory, this, refName, relevant);
ctl =
new RefControl(
changeDataFactory, refVisibilityControl, this, repositoryManager, refName, relevant);
refControls.put(refName, ctl);
}
return ctl;
@@ -164,7 +177,9 @@ class ProjectControl {
}
boolean allRefsAreVisible(Set<String> ignore) {
return user.isInternalUser() || canPerformOnAllRefs(Permission.READ, ignore);
return user.isInternalUser()
|| (!getProject().getNameKey().equals(allUsersName)
&& canPerformOnAllRefs(Permission.READ, ignore));
}
/** Can the user run upload pack? */
@@ -442,7 +457,7 @@ class ProjectControl {
return canPushToAtLeastOneRef();
case READ_CONFIG:
return controlForRef(RefNames.REFS_CONFIG).isVisible();
return controlForRef(RefNames.REFS_CONFIG).hasReadPermissionOnRef(false);
case BAN_COMMIT:
case READ_REFLOG:

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.permissions;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.Permission;
@@ -28,6 +29,7 @@ import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.logging.CallerFinder;
import com.google.gerrit.server.logging.LoggingContext;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -35,17 +37,24 @@ import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.MagicBranch;
import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
/** Manages access control for Git references (aka branches, tags). */
class RefControl {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final ChangeData.Factory changeDataFactory;
private final RefVisibilityControl refVisibilityControl;
private final ProjectControl projectControl;
private final GitRepositoryManager repositoryManager;
private final String refName;
/** All permissions that apply to this reference. */
@@ -58,15 +67,19 @@ class RefControl {
private Boolean owner;
private Boolean canForgeAuthor;
private Boolean canForgeCommitter;
private Boolean isVisible;
private Boolean hasReadPermissionOnRef;
RefControl(
ChangeData.Factory changeDataFactory,
RefVisibilityControl refVisibilityControl,
ProjectControl projectControl,
GitRepositoryManager repositoryManager,
String ref,
PermissionCollection relevant) {
this.changeDataFactory = changeDataFactory;
this.refVisibilityControl = refVisibilityControl;
this.projectControl = projectControl;
this.repositoryManager = repositoryManager;
this.refName = ref;
this.relevant = relevant;
this.callerFinder =
@@ -99,12 +112,27 @@ class RefControl {
return owner;
}
/** Can this user see this reference exists? */
boolean isVisible() {
if (isVisible == null) {
isVisible = getUser().isInternalUser() || canPerform(Permission.READ);
/**
* Returns {@code true} if the user has permission to read the ref. This method evaluates {@link
* RefPermission#READ} only. Hence, it is not authoritative. For example, it does not tell if the
* user can see NoteDb refs such as {@code refs/meta/external-ids} which requires {@link
* GlobalPermission#ACCESS_DATABASE} and deny access in this case.
*/
boolean hasReadPermissionOnRef(boolean allowNoteDbRefs) {
// Don't allow checking for NoteDb refs unless instructed otherwise.
if (!allowNoteDbRefs
&& (refName.startsWith(Constants.R_TAGS) || RefNames.isGerritRef(refName))) {
logger.atWarning().atMostEvery(30, TimeUnit.SECONDS).log(
"%s: Can't determine visibility of %s in RefControl. Denying access. "
+ "This case should have been handled before.",
projectControl.getProject().getName(), refName);
return false;
}
return isVisible;
if (hasReadPermissionOnRef == null) {
hasReadPermissionOnRef = getUser().isInternalUser() || canPerform(Permission.READ);
}
return hasReadPermissionOnRef;
}
/** @return true if this user can add a new patch set to this ref */
@@ -591,7 +619,10 @@ class RefControl {
private boolean can(RefPermission perm) throws PermissionBackendException {
switch (perm) {
case READ:
return isVisible();
if (refName.startsWith(Constants.R_TAGS)) {
return isTagVisible();
}
return refVisibilityControl.isVisible(projectControl, refName);
case CREATE:
// TODO This isn't an accurate test.
return canPerform(refPermissionName(perm));
@@ -641,6 +672,38 @@ class RefControl {
}
throw new PermissionBackendException(perm + " unsupported");
}
private boolean isTagVisible() throws PermissionBackendException {
if (projectControl.asForProject().test(ProjectPermission.READ)) {
// The user has READ on refs/* with no effective block permission. This is the broadest
// permission one can assign. There is no way to grant access to (specific) tags in Gerrit,
// so we have to assume that these users can see all tags because there could be tags that
// aren't reachable by any visible ref while the user can see all non-Gerrit refs. This
// matches Gerrit's historic behavior.
// This makes it so that these users could see commits that they can't see otherwise
// (e.g. a private change ref) if a tag was attached to it. Tags are meant to be used on
// the regular Git tree that users interact with, not on any of the Gerrit trees, so this
// is a negligible risk.
return true;
}
try (Repository repo =
repositoryManager.openRepository(projectControl.getProject().getNameKey())) {
// Tag visibility requires going through RefFilter because it entails loading all taggable
// refs and filtering them all by visibility.
Ref resolvedRef = repo.getRefDatabase().exactRef(refName);
if (resolvedRef == null) {
return false;
}
return projectControl.asForProject()
.filter(
ImmutableList.of(resolvedRef), repo, PermissionBackend.RefFilterOptions.defaults())
.stream()
.anyMatch(r -> refName.equals(r.getName()));
} catch (IOException e) {
throw new PermissionBackendException(e);
}
}
}
private static String refPermissionName(RefPermission refPermission) {

View File

@@ -0,0 +1,181 @@
// Copyright (C) 2020 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.permissions;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.entities.RefNames.REFS_CACHE_AUTOMERGE;
import com.google.common.base.Throwables;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.exceptions.NoSuchGroupException;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.jgit.lib.Constants;
/**
* This class is a component that is internal to {@link DefaultPermissionBackend}. It can
* authoritatively tell if a ref is accessible by a user.
*/
@Singleton
class RefVisibilityControl {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private final PermissionBackend permissionBackend;
private final GroupControl.GenericFactory groupControlFactory;
private final ChangeData.Factory changeDataFactory;
@Inject
RefVisibilityControl(
PermissionBackend permissionBackend,
GroupControl.GenericFactory groupControlFactory,
ChangeData.Factory changeDataFactory) {
this.permissionBackend = permissionBackend;
this.groupControlFactory = groupControlFactory;
this.changeDataFactory = changeDataFactory;
}
/**
* Returns an authoritative answer if the ref is visible to the user. Does not have support for
* tags and will throw a {@link PermissionBackendException} if asked for tags visibility.
*/
boolean isVisible(ProjectControl projectControl, String refName)
throws PermissionBackendException {
if (refName.startsWith(Constants.R_TAGS)) {
throw new PermissionBackendException(
"can't check tags through RefVisibilityControl. Use PermissionBackend#filter instead.");
}
if (!RefNames.isGerritRef(refName)) {
// This is not a special Gerrit ref and not a NoteDb ref. Likely, it's just a ref under
// refs/heads or another ref the user created. Apply the regular permissions with inheritance.
return projectControl.controlForRef(refName).hasReadPermissionOnRef(false);
}
if (refName.startsWith(REFS_CACHE_AUTOMERGE)) {
// Internal cache state that is accessible to no one.
return false;
}
boolean hasAccessDatabase =
permissionBackend
.user(projectControl.getUser())
.testOrFalse(GlobalPermission.ACCESS_DATABASE);
if (hasAccessDatabase) {
return true;
}
// Change and change edit visibility
Change.Id changeId;
if ((changeId = Change.Id.fromRef(refName)) != null) {
// Change ref is visible only if the change is visible.
ChangeData cd;
try {
cd = changeDataFactory.create(projectControl.getProject().getNameKey(), changeId);
checkState(cd.change().getId().equals(changeId));
} catch (StorageException e) {
if (Throwables.getCausalChain(e).stream()
.anyMatch(e2 -> e2 instanceof NoSuchChangeException)) {
// The change was deleted or is otherwise not accessible anymore.
// If the caller can see all refs and is allowed to see private changes on refs/, allow
// access. This is an escape hatch for receivers of "ref deleted" events.
PermissionBackend.ForProject forProject = projectControl.asForProject();
return forProject.test(ProjectPermission.READ)
&& forProject.ref("refs/").test(RefPermission.READ_PRIVATE_CHANGES);
}
throw new PermissionBackendException(e);
}
if (RefNames.isRefsEdit(refName)) {
// Edits are visible only to the owning user, if change is visible.
return visibleEdit(refName, projectControl, cd);
}
return projectControl.controlFor(cd).isVisible();
}
// Account visibility
CurrentUser user = projectControl.getUser();
Account.Id currentUserAccountId = user.isIdentifiedUser() ? user.getAccountId() : null;
Account.Id accountId;
if ((accountId = Account.Id.fromRef(refName)) != null) {
// Account ref is visible only to the corresponding account.
if (accountId.equals(currentUserAccountId)
&& projectControl.controlForRef(refName).hasReadPermissionOnRef(true)) {
return true;
}
return false;
}
// Group visibility
AccountGroup.UUID accountGroupUuid;
if ((accountGroupUuid = AccountGroup.UUID.fromRef(refName)) != null) {
// Group ref is visible only to the corresponding owner group.
try {
return projectControl.controlForRef(refName).hasReadPermissionOnRef(true)
&& groupControlFactory.controlFor(user, accountGroupUuid).isOwner();
} catch (NoSuchGroupException e) {
// The group is broken, but the ref is still around. Pretend the ref is not visible.
logger.atWarning().withCause(e).log("Found group ref %s but group isn't parsable", refName);
return false;
}
}
// We are done checking all cases where we would allow access to Gerrit-managed refs. Deny
// access in case we got this far.
logger.atFine().log(
"Denying access to %s because user doesn't have access to this Gerrit ref", refName);
return false;
}
private boolean visibleEdit(String refName, ProjectControl projectControl, ChangeData cd)
throws PermissionBackendException {
Change.Id id = Change.Id.fromEditRefPart(refName);
if (id == null) {
throw new IllegalStateException("unable to parse change id from edit ref " + refName);
}
if (!projectControl.controlFor(cd).isVisible()) {
// The user can't see the change so they can't see any edits.
return false;
}
if (projectControl.getUser().isIdentifiedUser()
&& refName.startsWith(
RefNames.refsEditPrefix(projectControl.getUser().asIdentifiedUser().getAccountId()))) {
logger.atFinest().log("Own change edit ref is visible: %s", refName);
return true;
}
try {
// Default to READ_PRIVATE_CHANGES as there is no special permission for reading edits.
projectControl
.asForProject()
.ref(cd.change().getDest().branch())
.check(RefPermission.READ_PRIVATE_CHANGES);
logger.atFinest().log("Foreign change edit ref is visible: %s", refName);
return true;
} catch (AuthException e) {
logger.atFinest().log("Foreign change edit ref is not visible: %s", refName);
return false;
}
}
}

View File

@@ -20,7 +20,6 @@ import java.net.Socket;
import java.net.UnknownHostException;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
@@ -32,19 +31,7 @@ public class BlindSSLSocketFactory extends SSLSocketFactory {
private static final BlindSSLSocketFactory INSTANCE;
static {
final X509TrustManager dummyTrustManager =
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
};
final X509TrustManager dummyTrustManager = new BlindTrustManager();
try {
final SSLContext context = SSLContext.getInstance("SSL");

View File

@@ -0,0 +1,33 @@
// Copyright (C) 2020 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.util.ssl;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
/** TrustManager implementation that accepts all certificates without validation. */
public class BlindTrustManager implements X509TrustManager {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
}

View File

@@ -17,68 +17,66 @@ package org.apache.commons.net.smtp;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import com.google.gerrit.util.ssl.BlindTrustManager;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class AuthSMTPClient extends SMTPClient {
/**
* SMTP Client with authentication support and optional SSL processing and verification. {@link
* org.apache.commons.net.smtp.SMTPSClient} is used for the SSL handshake and hostname verification.
*
* <p>If shouldHandshakeOnConnect mode is selected, SSL/TLS negotiation starts right after the
* connection has been established. Otherwise SSL/TLS negotiation will only occur if {@link
* AuthSMTPClient#execTLS} is explicitly called and the server accepts the command.
*
* <p>Examples:
*
* <ul>
* <li>For SSL connection:
* <pre>
* AuthSMTPClient c = new AuthSMTPClient(true, sslVerify);
* c.connect("127.0.0.1", 465);
* </pre>
* <li>For TLS connection:
* <pre>
* AuthSMTPClient c = new AuthSMTPClient(false, sslVerify);
* c.connect("127.0.0.1", 25);
* if (c.execTLS()) { /rest of the commands here/ }
* </pre>
* <li>If SSL encryption is not required:
* <pre>
* AuthSMTPClient c = new AuthSMTPClient(false, false);
* c.connect("127.0.0.1", port);
* </pre>
*/
public class AuthSMTPClient extends SMTPSClient {
private String authTypes;
public AuthSMTPClient(String charset) {
super(charset);
}
public void enableSSL(boolean verify) {
_socketFactory_ = sslFactory(verify);
}
public boolean startTLS(String hostname, int port, boolean verify)
throws SocketException, IOException {
if (sendCommand("STARTTLS") != 220) {
return false;
/**
* Constructs AuthSMTPClient.
*
* @param shouldHandshakeOnConnect the SSL processing mode, {@code true} if SSL negotiation should
* start right after connect, {@code false} if it will be started by the user explicitly or
* SSL negotiation is not required.
* @param sslVerificationEnabled {@code true} if the SMTP server's SSL certificate and hostname
* should be verified, {@code false} otherwise.
*/
public AuthSMTPClient(boolean shouldHandshakeOnConnect, boolean sslVerificationEnabled) {
// If SSL Encryption is required, SMTPSClient is used for the handshake.
// Otherwise, use SMTPSClient in 'explicit' mode without calling execTLS().
// See SMTPSClient._connectAction_ in commons-net-3.6.
super("TLS", shouldHandshakeOnConnect, UTF_8.name());
this.setEndpointCheckingEnabled(sslVerificationEnabled);
if (!sslVerificationEnabled) {
this.setTrustManager(new BlindTrustManager());
}
_socket_ = sslFactory(verify).createSocket(_socket_, hostname, port, true);
if (verify) {
SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
((SSLSocket) _socket_).setSSLParameters(sslParams);
}
// XXX: Can't call _connectAction_() because SMTP server doesn't
// give banner information again after STARTTLS, thus SMTP._connectAction_()
// will wait on __getReply() forever, see source code of commons-net-2.2.
//
// The lines below are copied from SocketClient._connectAction_() and
// SMTP._connectAction_() in commons-net-2.2.
_socket_.setSoTimeout(_timeout_);
_input_ = _socket_.getInputStream();
_output_ = _socket_.getOutputStream();
_reader = new BufferedReader(new InputStreamReader(_input_, UTF_8));
_writer = new BufferedWriter(new OutputStreamWriter(_output_, UTF_8));
return true;
}
private static SSLSocketFactory sslFactory(boolean verify) {
if (verify) {
return (SSLSocketFactory) SSLSocketFactory.getDefault();
}
return (SSLSocketFactory) BlindSSLSocketFactory.getDefault();
}
@Override

View File

@@ -1061,6 +1061,11 @@ public class ChangeIT extends AbstractDaemonTest {
com.google.gerrit.acceptance.TestAccount deleteAs)
throws Exception {
try {
projectOperations
.project(projectName)
.forUpdate()
.add(allow(Permission.VIEW_PRIVATE_CHANGES).ref("refs/*").group(ANONYMOUS_USERS))
.update();
requestScopeOperations.setApiUser(owner.id());
ChangeInput in = new ChangeInput();
in.project = projectName.get();

View File

@@ -1194,6 +1194,11 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void pushToDeletedGroupBranchIsRejectedForAllUsersRepo() throws Exception {
// refs/deleted-groups is only visible with ACCESS_DATABASE
projectOperations
.allProjectsForUpdate()
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
.update();
String groupRef =
RefNames.refsDeletedGroups(AccountGroup.uuid(gApi.groups().create(name("foo")).get().id));
createBranch(allUsers, groupRef);
@@ -1386,6 +1391,11 @@ public class GroupsIT extends AbstractDaemonTest {
@Test
public void cannotDeleteDeletedGroupBranch() throws Exception {
// refs/deleted-groups is only visible with ACCESS_DATABASE
projectOperations
.allProjectsForUpdate()
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
.update();
String groupRef = RefNames.refsDeletedGroups(AccountGroup.uuid(name("foo")));
createBranch(allUsers, groupRef);
testCannotDeleteGroupBranch(RefNames.REFS_DELETED_GROUPS + "*", groupRef);

View File

@@ -31,7 +31,9 @@ import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.PushResult;
@@ -52,6 +54,13 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
}
}
@ConfigSuite.Config
public static Config skipFalse() {
Config config = new Config();
config.setBoolean("auth", null, "skipFullRefEvaluationIfAllRefsAreVisible", false);
return config;
}
@Inject private ProjectOperations projectOperations;
private RevCommit initialHead;
@@ -92,6 +101,20 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
removePushFromRefsTags();
}
@Test
public void createTagForExistingCommit_withoutGlobalReadPermissions() throws Exception {
removeReadAccessOnRefsStar();
grantReadAccessOnRefsHeadsStar();
createTagForExistingCommit();
}
@Test
public void createTagForNewCommit_withoutGlobalReadPermissions() throws Exception {
removeReadAccessOnRefsStar();
grantReadAccessOnRefsHeadsStar();
createTagForNewCommit();
}
@Test
public void fastForward() throws Exception {
allowTagCreation();
@@ -109,6 +132,15 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
fastForwardTagToExistingCommit(tagName, expectedStatus);
fastForwardTagToNewCommit(tagName, expectedStatus);
// Above we just fast-forwarded the tag to a new commit which is not part of any branch. By
// default this tag is not visible, as users can only see tags that point to commits that are
// part of visible branches, which is not the case for this tag. It's odd that we allow the user
// to create such a tag that is then not visible to the creator. Below we want to fast-forward
// this tag, but this is only possible if the tag is visible. To make it visible we must allow
// the user to read all tags, regardless of whether it points to a commit that is part of a
// visible branch.
allowReadingAllTag();
allowForcePushOnRefsTags();
fastForwardTagToExistingCommit(tagName, Status.OK);
fastForwardTagToNewCommit(tagName, Status.OK);
@@ -234,6 +266,49 @@ public abstract class AbstractPushTag extends AbstractDaemonTest {
assertWithMessage(tagType.name()).that(refUpdate.getStatus()).isEqualTo(expectedStatus);
}
private void removeReadAccessOnRefsStar() {
projectOperations
.project(allProjects)
.forUpdate()
.remove(permissionKey(Permission.READ).ref("refs/*"))
.update();
projectOperations
.project(project)
.forUpdate()
.remove(permissionKey(Permission.READ).ref("refs/*"))
.update();
}
private void grantReadAccessOnRefsHeadsStar() {
projectOperations
.project(project)
.forUpdate()
.add(allow(Permission.READ).ref("refs/heads/*").group(REGISTERED_USERS))
.update();
}
private void allowReadingAllTag() throws Exception {
// Tags are only visible if the commits to which they point are part of a visible branch.
// To make all tags visible, including tags that point to commits that are not part of a visible
// branch, either auth.skipFullRefEvaluationIfAllRefsAreVisible in gerrit.config needs to be
// true, or the user must have READ access for all refs in the repository.
if (cfg.getBoolean("auth", "skipFullRefEvaluationIfAllRefsAreVisible", true)) {
return;
}
// By default READ access in the All-Projects project is granted to registered users on refs/*,
// which makes all refs, except refs/meta/config, visible to them. refs/meta/config is not
// visible since by default READ access to it is exclusively granted to the project owners only.
// This means to make all refs, and thus all tags, visible, we must allow registered users to
// see the refs/meta/config branch.
projectOperations
.project(project)
.forUpdate()
.add(allow(Permission.READ).ref("refs/meta/config").group(REGISTERED_USERS))
.update();
}
private void allowTagCreation() throws Exception {
projectOperations
.project(project)

View File

@@ -0,0 +1,594 @@
// Copyright (C) 2020 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.acceptance.rest.project;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowCapability;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.capabilityKey;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.notedb.Sequences;
import com.google.gerrit.testing.ConfigSuite;
import com.google.inject.Inject;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
public class GetBranchIT extends AbstractDaemonTest {
@Inject private ChangeOperations changeOperations;
@Inject private GroupOperations groupOperations;
@Inject private ProjectOperations projectOperations;
@Inject private RequestScopeOperations requestScopeOperations;
@ConfigSuite.Config
public static Config skipFalse() {
Config config = new Config();
config.setBoolean("auth", null, "skipFullRefEvaluationIfAllRefsAreVisible", false);
return config;
}
@Test
public void cannotGetNonExistingBranch() {
assertBranchNotFound(project, RefNames.fullName("non-existing"));
}
@Test
public void getBranch() throws Exception {
requestScopeOperations.setApiUser(user.id());
assertBranchFound(project, RefNames.fullName("master"));
}
@Test
public void getBranchByShortName() throws Exception {
requestScopeOperations.setApiUser(user.id());
assertBranchFound(project, "master");
}
@Test
public void cannotGetNonVisibleBranch() {
String branchName = "master";
// block read access to the branch
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
.update();
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, RefNames.fullName(branchName));
}
@Test
public void cannotGetNonVisibleBranchByShortName() {
String branchName = "master";
// block read access to the branch
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
.update();
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, branchName);
}
@Test
public void getChangeRef() throws Exception {
// create a change
Change.Id changeId = changeOperations.newChange().project(project).create();
// a user without the 'Access Database' capability can see the change ref
requestScopeOperations.setApiUser(user.id());
String changeRef = RefNames.patchSetRef(PatchSet.id(changeId, 1));
assertBranchFound(project, changeRef);
}
@Test
public void getChangeRefOfNonVisibleChange() throws Exception {
// create a change
String branchName = "master";
Change.Id changeId = changeOperations.newChange().project(project).branch(branchName).create();
// block read access to the branch
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
.update();
// a user without the 'Access Database' capability cannot see the change ref
requestScopeOperations.setApiUser(user.id());
String changeRef = RefNames.patchSetRef(PatchSet.id(changeId, 1));
assertBranchNotFound(project, changeRef);
// a user with the 'Access Database' capability can see the change ref
testGetRefWithAccessDatabase(project, changeRef);
}
@Test
public void getChangeEditRef() throws Exception {
TestAccount user2 = accountCreator.user2();
// create a change
Change.Id changeId = changeOperations.newChange().project(project).create();
// create a change edit by 'user'
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(changeId.get()).edit().create();
// every user can see their own change edit refs
String changeEditRef = RefNames.refsEdit(user.id(), changeId, PatchSet.id(changeId, 1));
assertBranchFound(project, changeEditRef);
// a user without the 'Access Database' capability cannot see the change edit ref of another
// user
requestScopeOperations.setApiUser(user2.id());
assertBranchNotFound(project, changeEditRef);
// a user with the 'Access Database' capability can see the change edit ref of another user
testGetRefWithAccessDatabase(project, changeEditRef);
}
@Test
public void cannotGetChangeEditRefOfNonVisibleChange() throws Exception {
// create a change
String branchName = "master";
Change.Id changeId = changeOperations.newChange().project(project).branch(branchName).create();
// create a change edit by 'user'
requestScopeOperations.setApiUser(user.id());
gApi.changes().id(changeId.get()).edit().create();
// make the change non-visible by blocking read access on the destination
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref(RefNames.fullName(branchName)).group(ANONYMOUS_USERS))
.update();
// user cannot see their own change edit refs if the change is no longer visible
String changeEditRef = RefNames.refsEdit(user.id(), changeId, PatchSet.id(changeId, 1));
assertBranchNotFound(project, changeEditRef);
// a user with the 'Access Database' capability can see the change edit ref
testGetRefWithAccessDatabase(project, changeEditRef);
}
@Test
public void getChangeMetaRef() throws Exception {
// create a change
Change.Id changeId = changeOperations.newChange().project(project).create();
// A user without the 'Access Database' capability can see the change meta ref.
// This may be surprising, as 'Access Database' guards access to meta refs and the change meta
// ref is a meta ref, however change meta refs have been always visible to all users that can
// see the change and some tools rely on seeing these refs, so we have to keep the current
// behaviour.
requestScopeOperations.setApiUser(user.id());
String changeMetaRef = RefNames.changeMetaRef(changeId);
assertBranchFound(project, changeMetaRef);
}
@Test
public void getRefsMetaConfig() throws Exception {
// a non-project owner cannot get the refs/meta/config branch
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, RefNames.REFS_CONFIG);
// a non-project owner cannot get the refs/meta/config branch even with the 'Access Database'
// capability
projectOperations
.project(allProjects)
.forUpdate()
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
.update();
try {
assertBranchNotFound(project, RefNames.REFS_CONFIG);
} finally {
projectOperations
.allProjectsForUpdate()
.remove(
capabilityKey(GlobalCapability.ACCESS_DATABASE)
.group(SystemGroupBackend.REGISTERED_USERS))
.update();
}
requestScopeOperations.setApiUser(user.id());
// a project owner can get the refs/meta/config branch
projectOperations
.project(project)
.forUpdate()
.add(allow(Permission.OWNER).ref("refs/*").group(REGISTERED_USERS))
.update();
assertBranchFound(project, RefNames.REFS_CONFIG);
}
@Test
public void getUserRefOfOtherUser() throws Exception {
String userRef = RefNames.refsUsers(admin.id());
// a user without the 'Access Database' capability cannot see the user ref of another user
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(allUsers, userRef);
// a user with the 'Access Database' capability can see the user ref of another user
testGetRefWithAccessDatabase(allUsers, userRef);
}
@Test
public void getOwnUserRef() throws Exception {
// every user can see the own user ref
requestScopeOperations.setApiUser(user.id());
assertBranchFound(allUsers, RefNames.refsUsers(user.id()));
// TODO: every user can see the own user ref via the magic ref/users/self ref
// requestScopeOperations.setApiUser(user.id());
// assertBranchFound(allUsers, RefNames.REFS_USERS_SELF);
}
@Test
public void getExternalIdsRefs() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/meta/external-ids ref
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(allUsers, RefNames.REFS_EXTERNAL_IDS);
// a user with the 'Access Database' capability can see the refs/meta/external-ids ref
testGetRefWithAccessDatabase(allUsers, RefNames.REFS_EXTERNAL_IDS);
}
@Test
public void getGroupRef() throws Exception {
// create a group
AccountGroup.UUID ownerGroupUuid =
groupOperations.newGroup().name("owner-group").addMember(admin.id()).create();
AccountGroup.UUID testGroupUuid =
groupOperations.newGroup().name("test-group").ownerGroupUuid(ownerGroupUuid).create();
// a non-group owner without the 'Access Database' capability cannot see the group ref
requestScopeOperations.setApiUser(user.id());
String groupRef = RefNames.refsGroups(testGroupUuid);
assertBranchNotFound(allUsers, groupRef);
// a non-group owner with the 'Access Database' capability can see the group ref
testGetRefWithAccessDatabase(allUsers, groupRef);
// a group owner can see the group ref if the group ref is visible
groupOperations.group(ownerGroupUuid).forUpdate().addMember(user.id()).update();
assertBranchFound(allUsers, groupRef);
// A group owner cannot see the group ref if the group ref is not visible.
// The READ access for refs/groups/* must be blocked on All-Projects rather than All-Users.
// This is because READ access for refs/groups/* on All-Users is by default granted to
// REGISTERED_USERS, and if an ALLOW rule and a BLOCK rule are on the same project and ref,
// the ALLOW rule takes precedence.
projectOperations
.project(allProjects)
.forUpdate()
.add(block(Permission.READ).ref("refs/groups/*").group(ANONYMOUS_USERS))
.update();
assertBranchNotFound(allUsers, groupRef);
}
@Test
public void getGroupNamesRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/meta/group-names ref
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(allUsers, RefNames.REFS_GROUPNAMES);
// a user with the 'Access Database' capability can see the refs/meta/group-names ref
testGetRefWithAccessDatabase(allUsers, RefNames.REFS_GROUPNAMES);
}
@Test
public void getDeletedGroupRef() throws Exception {
// Create a deleted group ref. We must create a directly in the repo, since group deletion is
// not supported yet.
String deletedGroupRef = RefNames.refsDeletedGroups(AccountGroup.uuid("deleted-group"));
try (TestRepository<Repository> testRepo =
new TestRepository<>(repoManager.openRepository(allUsers))) {
testRepo
.branch(deletedGroupRef)
.commit()
.message("Some Message")
.add("group.config", "content")
.create();
}
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(allUsers, deletedGroupRef);
// a user with the 'Access Database' capability can see the deleted group ref
testGetRefWithAccessDatabase(allUsers, deletedGroupRef);
}
@Test
public void getDraftCommentsRef() throws Exception {
TestAccount user2 = accountCreator.user2();
// create a change
String fileName = "a.txt";
Change change = createChange("A Change", fileName, "content").getChange().change();
// create a draft comment by the by 'user'
requestScopeOperations.setApiUser(user.id());
DraftInput draftInput = new DraftInput();
draftInput.path = fileName;
draftInput.line = 0;
draftInput.message = "Some Comment";
gApi.changes().id(change.getChangeId()).current().createDraft(draftInput);
// every user can see their own draft comments refs
// TODO: is this a bug?
String draftCommentsRef = RefNames.refsDraftComments(change.getId(), user.id());
assertBranchFound(allUsers, draftCommentsRef);
// a user without the 'Access Database' capability cannot see the draft comments ref of another
// user
requestScopeOperations.setApiUser(user2.id());
assertBranchNotFound(allUsers, draftCommentsRef);
// a user with the 'Access Database' capability can see the draft comments ref of another user
testGetRefWithAccessDatabase(allUsers, draftCommentsRef);
}
@Test
public void getStarredChangesRef() throws Exception {
TestAccount user2 = accountCreator.user2();
// create a change
Change change = createChange().getChange().change();
// let user star the change
requestScopeOperations.setApiUser(user.id());
gApi.accounts().self().starChange(Integer.toString(change.getChangeId()));
// every user can see their own starred changes refs
// TODO: is this a bug?
String starredChangesRef = RefNames.refsStarredChanges(change.getId(), user.id());
assertBranchFound(allUsers, starredChangesRef);
// a user without the 'Access Database' capability cannot see the starred changes ref of another
// user
requestScopeOperations.setApiUser(user2.id());
assertBranchNotFound(allUsers, starredChangesRef);
// a user with the 'Access Database' capability can see the starred changes ref of another user
testGetRefWithAccessDatabase(allUsers, starredChangesRef);
}
@Test
public void getTagRef() throws Exception {
// create a tag
TagInput input = new TagInput();
input.message = "My Tag";
input.revision = projectOperations.project(project).getHead("master").name();
TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get();
// any user who can see the project, can see the tag
requestScopeOperations.setApiUser(user.id());
assertBranchFound(project, tagInfo.ref);
}
@Test
public void cannotGetTagRefThatPointsToNonVisibleBranch() throws Exception {
// create a tag
TagInput input = new TagInput();
input.message = "My Tag";
input.revision = projectOperations.project(project).getHead("master").name();
TagInfo tagInfo = gApi.projects().name(project.get()).tag("my-tag").create(input).get();
// block read access to the branch
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref(RefNames.fullName("master")).group(ANONYMOUS_USERS))
.update();
// if the user cannot see the project, the tag is not visible
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, tagInfo.ref);
}
@Test
public void getSymbolicRef() throws Exception {
// 'HEAD' is visible since it points to 'master' that is visible
requestScopeOperations.setApiUser(user.id());
assertBranchFound(project, "HEAD");
}
@Test
public void cannotGetSymbolicRefThatPointsToNonVisibleBranch() {
// block read access to the branch to which HEAD points by default
projectOperations
.project(project)
.forUpdate()
.add(block(Permission.READ).ref(RefNames.fullName("master")).group(ANONYMOUS_USERS))
.update();
// since 'master' is not visible, 'HEAD' which points to 'master' is also not visible
requestScopeOperations.setApiUser(user.id());
assertBranchNotFound(project, "HEAD");
}
@Test
public void getAccountSequenceRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/sequences/accounts ref
requestScopeOperations.setApiUser(user.id());
String accountSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS;
assertBranchNotFound(allUsers, accountSequenceRef);
// a user with the 'Access Database' capability can see the refs/sequences/accounts ref
testGetRefWithAccessDatabase(allUsers, accountSequenceRef);
}
@Test
public void getChangeSequenceRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/sequences/changes ref
requestScopeOperations.setApiUser(user.id());
String changeSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_CHANGES;
assertBranchNotFound(allProjects, changeSequenceRef);
// a user with the 'Access Database' capability can see the refs/sequences/changes ref
testGetRefWithAccessDatabase(allProjects, changeSequenceRef);
}
@Test
public void getGroupSequenceRef() throws Exception {
// a user without the 'Access Database' capability cannot see the refs/sequences/groups ref
requestScopeOperations.setApiUser(user.id());
String groupSequenceRef = RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS;
assertBranchNotFound(allUsers, groupSequenceRef);
// a user with the 'Access Database' capability can see the refs/sequences/groups ref
testGetRefWithAccessDatabase(allUsers, groupSequenceRef);
}
@Test
public void getVersionMetaRef() throws Exception {
// TODO: a user without the 'Access Database' capability cannot see the refs/meta/version ref
// requestScopeOperations.setApiUser(user.id());
// assertBranchNotFound(allProjects, RefNames.REFS_VERSION);
// a user with the 'Access Database' capability can see the refs/meta/vaersion ref
testGetRefWithAccessDatabase(allProjects, RefNames.REFS_VERSION);
}
@Test
public void cannotGetAutoMergeRef() throws Exception {
String file = "foo/a.txt";
// Create a base change.
Change.Id baseChange =
changeOperations
.newChange()
.project(project)
.branch("master")
.file(file)
.content("base content")
.create();
approve(Integer.toString(baseChange.get()));
gApi.changes().id(baseChange.get()).current().submit();
// Create another branch
String branchName = "foo";
createBranchWithRevision(
BranchNameKey.create(project, branchName),
projectOperations.project(project).getHead("master").name());
// Create a change in master that touches the file.
Change.Id changeInMaster =
changeOperations
.newChange()
.project(project)
.branch("master")
.file(file)
.content("master content")
.create();
approve(Integer.toString(changeInMaster.get()));
gApi.changes().id(changeInMaster.get()).current().submit();
// Create a change in the other branch and that touches the file.
Change.Id changeInOtherBranch =
changeOperations
.newChange()
.project(project)
.branch(branchName)
.file(file)
.content("other content")
.create();
approve(Integer.toString(changeInOtherBranch.get()));
gApi.changes().id(changeInOtherBranch.get()).current().submit();
// Create a merge change with a conflict resolution for the file.
Change.Id mergeChange =
changeOperations
.newChange()
.project(project)
.branch("master")
.mergeOfButBaseOnFirst()
.tipOfBranch("master")
.and()
.tipOfBranch(branchName)
.file(file)
.content("merged content")
.create();
String mergeRevision =
changeOperations.change(mergeChange).currentPatchset().get().commitId().name();
assertBranchNotFound(project, RefNames.refsCacheAutomerge(mergeRevision));
}
private void testGetRefWithAccessDatabase(Project.NameKey project, String ref)
throws RestApiException {
projectOperations
.project(allProjects)
.forUpdate()
.add(allowCapability(GlobalCapability.ACCESS_DATABASE).group(REGISTERED_USERS))
.update();
try {
requestScopeOperations.setApiUser(user.id());
assertBranchFound(project, ref);
} finally {
projectOperations
.allProjectsForUpdate()
.remove(
capabilityKey(GlobalCapability.ACCESS_DATABASE)
.group(SystemGroupBackend.REGISTERED_USERS))
.update();
}
}
private void assertBranchNotFound(Project.NameKey project, String ref) {
ResourceNotFoundException exception =
assertThrows(
ResourceNotFoundException.class,
() -> gApi.projects().name(project.get()).branch(ref).get());
assertThat(exception).hasMessageThat().isEqualTo("Not found: " + ref);
}
private void assertBranchFound(Project.NameKey project, String ref) throws RestApiException {
BranchInfo branchInfo = gApi.projects().name(project.get()).branch(ref).get();
assertThat(branchInfo.ref).isEqualTo(RefNames.fullName(ref));
}
}

View File

@@ -17,8 +17,11 @@ java_library(
"//lib:junit",
"//lib/guice",
"//lib/httpcomponents:httpcore",
"//lib/jackson:jackson-annotations",
"//lib/log:api",
"//lib/testcontainers",
"//lib/testcontainers:docker-java-api",
"//lib/testcontainers:docker-java-transport",
"//lib/testcontainers:testcontainers-elasticsearch",
],
)

View File

@@ -19,6 +19,7 @@ import org.junit.AssumptionViolatedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.utility.DockerImageName;
/* Helper class for running ES integration tests in docker container */
public class ElasticContainer extends ElasticsearchContainer {
@@ -39,7 +40,7 @@ public class ElasticContainer extends ElasticsearchContainer {
private static String getImageName(ElasticVersion version) {
switch (version) {
case V6_8:
return "blacktop/elasticsearch:6.8.12";
return "blacktop/elasticsearch:6.8.13";
case V7_0:
return "blacktop/elasticsearch:7.0.1";
case V7_1:
@@ -63,7 +64,9 @@ public class ElasticContainer extends ElasticsearchContainer {
}
private ElasticContainer(ElasticVersion version) {
super(getImageName(version));
super(
DockerImageName.parse(getImageName(version))
.asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"));
}
@Override

View File

@@ -48,6 +48,7 @@ import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupMembership;
import com.google.gerrit.server.account.ListGroupMembership;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
import com.google.gerrit.server.project.ProjectCache;
@@ -63,6 +64,7 @@ import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
@@ -90,6 +92,18 @@ public class RefControlTest {
assertWithMessage("not owner").that(u.isOwner()).isFalse();
}
private void assertAllRefsAreVisible(ProjectControl u) {
assertWithMessage("all refs visible")
.that(u.allRefsAreVisible(Collections.emptySet()))
.isTrue();
}
private void assertAllRefsAreNotVisible(ProjectControl u) {
assertWithMessage("all refs NOT visible")
.that(u.allRefsAreVisible(Collections.emptySet()))
.isFalse();
}
private void assertNotOwner(String ref, ProjectControl u) {
assertWithMessage("NOT OWN " + ref).that(u.controlForRef(ref).isOwner()).isFalse();
}
@@ -105,11 +119,21 @@ public class RefControlTest {
}
private void assertCanRead(String ref, ProjectControl u) {
assertWithMessage("can read " + ref).that(u.controlForRef(ref).isVisible()).isTrue();
assertWithMessage("can read " + ref)
.that(
u.controlForRef(ref)
.hasReadPermissionOnRef(
true)) // This should be false but the test relies on inheritance into refs/tags
.isTrue();
}
private void assertCannotRead(String ref, ProjectControl u) {
assertWithMessage("cannot read " + ref).that(u.controlForRef(ref).isVisible()).isFalse();
assertWithMessage("cannot read " + ref)
.that(
u.controlForRef(ref)
.hasReadPermissionOnRef(
true)) // This should be false but the test relies on inheritance into refs/tags
.isFalse();
}
private void assertCanSubmit(String ref, ProjectControl u) {
@@ -171,6 +195,7 @@ public class RefControlTest {
private final Project.NameKey parentKey = Project.nameKey("parent");
@Inject private AllProjectsName allProjectsName;
@Inject private AllUsersName allUsersName;
@Inject private InMemoryRepositoryManager repoManager;
@Inject private MetaDataUpdate.Server metaDataUpdateFactory;
@Inject private ProjectCache projectCache;
@@ -261,6 +286,32 @@ public class RefControlTest {
assertAdminsAreOwnersAndDevsAreNot();
}
@Test
public void allRefsAreVisibleForRegularProject() throws Exception {
projectOperations
.project(localKey)
.forUpdate()
.add(allow(READ).ref("refs/*").group(DEVS))
.add(allow(READ).ref("refs/groups/*").group(DEVS))
.add(allow(READ).ref("refs/users/default").group(DEVS))
.update();
assertAllRefsAreVisible(user(localKey, DEVS));
}
@Test
public void allRefsAreNotVisibleForAllUsers() throws Exception {
projectOperations
.project(allUsersName)
.forUpdate()
.add(allow(READ).ref("refs/*").group(DEVS))
.add(allow(READ).ref("refs/groups/*").group(DEVS))
.add(allow(READ).ref("refs/users/default").group(DEVS))
.update();
assertAllRefsAreNotVisible(user(allUsersName, DEVS));
}
@Test
public void branchDelegation1() throws Exception {
projectOperations

View File

@@ -1,5 +1,13 @@
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "jackson-annotations",
testonly = True,
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
exports = ["@jackson-annotations//jar"],
)
java_library(
name = "jackson-core",
data = ["//lib:LICENSE-Apache2.0"],

View File

@@ -12,6 +12,8 @@ grep 'name = "[^"]*"' ${bzl} | sed 's|^[^"]*"||g;s|".*$||g' | sort > $TMP/names
cat << EOF > $TMP/want
cglib-3_2
docker-java-api
docker-java-transport
dropwizard-core
duct-tape
eddsa
@@ -22,6 +24,7 @@ flogger-system-backend
httpasyncclient
httpcore-nio
j2objc
jackson-annotations
jackson-core
jna
jruby

View File

@@ -1,5 +1,21 @@
load("@rules_java//java:defs.bzl", "java_library")
java_library(
name = "docker-java-api",
testonly = True,
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
exports = ["@docker-java-api//jar"],
)
java_library(
name = "docker-java-transport",
testonly = True,
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//visibility:public"],
exports = ["@docker-java-transport//jar"],
)
java_library(
name = "duct-tape",
testonly = True,

View File

@@ -296,6 +296,11 @@ test -n "$USE_LFS" && FDS_MULTIPLIER=3
GERRIT_FDS=`expr $FDS_MULTIPLIER \* $GERRIT_FDS`
test $GERRIT_FDS -lt 1024 && GERRIT_FDS=1024
CACHE_FDS=`get_config --get cache.openFiles`
if test -n "$CACHE_FDS"; then
GERRIT_FDS=`expr $CACHE_FDS \+ $GERRIT_FDS`
fi
GERRIT_STARTUP_TIMEOUT=`get_config --get container.startupTimeout`
test -z "$GERRIT_STARTUP_TIMEOUT" && GERRIT_STARTUP_TIMEOUT=90 # seconds

View File

@@ -23,8 +23,8 @@ def declare_nongoogle_deps():
maven_jar(
name = "dropwizard-core",
artifact = "io.dropwizard.metrics:metrics-core:4.1.14",
sha1 = "14cf9dd67619a0390812dddb232df339e3383d35",
artifact = "io.dropwizard.metrics:metrics-core:4.1.12.1",
sha1 = "cb2f351bf4463751201f43bb99865235d5ba07ca",
)
SSHD_VERS = "2.4.0"
@@ -143,18 +143,40 @@ def declare_nongoogle_deps():
sha1 = "dc13ae4faca6df981fc7aeb5a522d9db446d5d50",
)
TESTCONTAINERS_VERSION = "1.14.3"
DOCKER_JAVA_VERS = "3.2.5"
maven_jar(
name = "docker-java-api",
artifact = "com.github.docker-java:docker-java-api:" + DOCKER_JAVA_VERS,
sha1 = "8fe5c5e39f940ce58620e77cedc0a2a52d76f9d8",
)
maven_jar(
name = "docker-java-transport",
artifact = "com.github.docker-java:docker-java-transport:" + DOCKER_JAVA_VERS,
sha1 = "27af0ee7ebc2f5672e23ea64769497b5d55ce3ac",
)
# https://github.com/docker-java/docker-java/blob/3.2.5/pom.xml#L61
# <=> DOCKER_JAVA_VERS
maven_jar(
name = "jackson-annotations",
artifact = "com.fasterxml.jackson.core:jackson-annotations:2.10.3",
sha1 = "0f63b3b1da563767d04d2e4d3fc1ae0cdeffebe7",
)
TESTCONTAINERS_VERSION = "1.15.0"
maven_jar(
name = "testcontainers",
artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
sha1 = "071fc82ba663f469447a19434e7db90f3a872753",
sha1 = "b627535b444d88e7b14953bb953d80d9b7b3bd76",
)
maven_jar(
name = "testcontainers-elasticsearch",
artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
sha1 = "3709e2ebb0b6aa4e2ba2b6ca92ffdd3bf637a86c",
sha1 = "2bd79fd915e5c7bcf9b5d86cd8e0b7a0fff4b8ce",
)
maven_jar(