diff options
Diffstat (limited to 'java/com/google/gerrit/server/events/EventBroker.java')
-rw-r--r-- | java/com/google/gerrit/server/events/EventBroker.java | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/events/EventBroker.java b/java/com/google/gerrit/server/events/EventBroker.java new file mode 100644 index 0000000000..94e9bb1fe4 --- /dev/null +++ b/java/com/google/gerrit/server/events/EventBroker.java @@ -0,0 +1,228 @@ +// Copyright (C) 2016 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.events; + +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.extensions.restapi.AuthException; +import com.google.gerrit.lifecycle.LifecycleModule; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.PatchSet; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.notedb.ChangeNotes; +import com.google.gerrit.server.permissions.ChangePermission; +import com.google.gerrit.server.permissions.PermissionBackend; +import com.google.gerrit.server.permissions.PermissionBackendException; +import com.google.gerrit.server.permissions.ProjectPermission; +import com.google.gerrit.server.permissions.RefPermission; +import com.google.gerrit.server.plugincontext.PluginSetContext; +import com.google.gerrit.server.plugincontext.PluginSetEntryContext; +import com.google.gerrit.server.project.NoSuchChangeException; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.gwtorm.server.OrmException; +import com.google.inject.Inject; +import com.google.inject.Provider; +import com.google.inject.Singleton; + +/** Distributes Events to listeners if they are allowed to see them */ +@Singleton +public class EventBroker implements EventDispatcher { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + public static class Module extends LifecycleModule { + @Override + protected void configure() { + DynamicItem.itemOf(binder(), EventDispatcher.class); + DynamicItem.bind(binder(), EventDispatcher.class).to(EventBroker.class); + } + } + + /** Listeners to receive changes as they happen (limited by visibility of user). */ + protected final PluginSetContext<UserScopedEventListener> listeners; + + /** Listeners to receive all changes as they happen. */ + protected final PluginSetContext<EventListener> unrestrictedListeners; + + private final PermissionBackend permissionBackend; + protected final ProjectCache projectCache; + + protected final ChangeNotes.Factory notesFactory; + + protected final Provider<ReviewDb> dbProvider; + + @Inject + public EventBroker( + PluginSetContext<UserScopedEventListener> listeners, + PluginSetContext<EventListener> unrestrictedListeners, + PermissionBackend permissionBackend, + ProjectCache projectCache, + ChangeNotes.Factory notesFactory, + Provider<ReviewDb> dbProvider) { + this.listeners = listeners; + this.unrestrictedListeners = unrestrictedListeners; + this.permissionBackend = permissionBackend; + this.projectCache = projectCache; + this.notesFactory = notesFactory; + this.dbProvider = dbProvider; + } + + @Override + public void postEvent(Change change, ChangeEvent event) + throws OrmException, PermissionBackendException { + fireEvent(change, event); + } + + @Override + public void postEvent(Branch.NameKey branchName, RefEvent event) + throws PermissionBackendException { + fireEvent(branchName, event); + } + + @Override + public void postEvent(Project.NameKey projectName, ProjectEvent event) { + fireEvent(projectName, event); + } + + @Override + public void postEvent(Event event) throws OrmException, PermissionBackendException { + fireEvent(event); + } + + protected void fireEventForUnrestrictedListeners(Event event) { + unrestrictedListeners.runEach(l -> l.onEvent(event)); + } + + protected void fireEvent(Change change, ChangeEvent event) + throws OrmException, PermissionBackendException { + for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { + CurrentUser user = c.call(l -> l.getUser()); + if (isVisibleTo(change, user)) { + c.run(l -> l.onEvent(event)); + } + } + fireEventForUnrestrictedListeners(event); + } + + protected void fireEvent(Project.NameKey project, ProjectEvent event) { + for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { + CurrentUser user = c.call(l -> l.getUser()); + if (isVisibleTo(project, user)) { + c.run(l -> l.onEvent(event)); + } + } + fireEventForUnrestrictedListeners(event); + } + + protected void fireEvent(Branch.NameKey branchName, RefEvent event) + throws PermissionBackendException { + for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { + CurrentUser user = c.call(l -> l.getUser()); + if (isVisibleTo(branchName, user)) { + c.run(l -> l.onEvent(event)); + } + } + fireEventForUnrestrictedListeners(event); + } + + protected void fireEvent(Event event) throws OrmException, PermissionBackendException { + for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { + CurrentUser user = c.call(l -> l.getUser()); + if (isVisibleTo(event, user)) { + c.run(l -> l.onEvent(event)); + } + } + fireEventForUnrestrictedListeners(event); + } + + protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) { + try { + ProjectState state = projectCache.get(project); + if (state == null || !state.statePermitsRead()) { + return false; + } + + permissionBackend.user(user).project(project).check(ProjectPermission.ACCESS); + return true; + } catch (AuthException | PermissionBackendException e) { + return false; + } + } + + protected boolean isVisibleTo(Change change, CurrentUser user) + throws OrmException, PermissionBackendException { + if (change == null) { + return false; + } + ProjectState pe = projectCache.get(change.getProject()); + if (pe == null || !pe.statePermitsRead()) { + return false; + } + ReviewDb db = dbProvider.get(); + try { + permissionBackend + .user(user) + .change(notesFactory.createChecked(db, change)) + .database(db) + .check(ChangePermission.READ); + return true; + } catch (AuthException e) { + return false; + } + } + + protected boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) + throws PermissionBackendException { + ProjectState pe = projectCache.get(branchName.getParentKey()); + if (pe == null || !pe.statePermitsRead()) { + return false; + } + + try { + permissionBackend.user(user).ref(branchName).check(RefPermission.READ); + return true; + } catch (AuthException e) { + return false; + } + } + + protected boolean isVisibleTo(Event event, CurrentUser user) + throws OrmException, PermissionBackendException { + if (event instanceof RefEvent) { + RefEvent refEvent = (RefEvent) event; + String ref = refEvent.getRefName(); + if (PatchSet.isChangeRef(ref)) { + Change.Id cid = PatchSet.Id.fromRef(ref).getParentKey(); + try { + Change change = + notesFactory + .createChecked(dbProvider.get(), refEvent.getProjectNameKey(), cid) + .getChange(); + return isVisibleTo(change, user); + } catch (NoSuchChangeException e) { + logger.atFine().log( + "Change %s cannot be found, falling back on ref visibility check", cid.id); + } + } + return isVisibleTo(refEvent.getBranchNameKey(), user); + } else if (event instanceof ProjectEvent) { + return isVisibleTo(((ProjectEvent) event).getProjectNameKey(), user); + } + return true; + } +} |