Line data Source code
1 : // Copyright (C) 2016 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.events; 16 : 17 : import com.google.common.base.Strings; 18 : import com.google.common.flogger.FluentLogger; 19 : import com.google.gerrit.common.Nullable; 20 : import com.google.gerrit.entities.BranchNameKey; 21 : import com.google.gerrit.entities.Change; 22 : import com.google.gerrit.entities.PatchSet; 23 : import com.google.gerrit.entities.Project; 24 : import com.google.gerrit.extensions.registration.DynamicItem; 25 : import com.google.gerrit.lifecycle.LifecycleModule; 26 : import com.google.gerrit.server.CurrentUser; 27 : import com.google.gerrit.server.config.GerritInstanceId; 28 : import com.google.gerrit.server.notedb.ChangeNotes; 29 : import com.google.gerrit.server.permissions.ChangePermission; 30 : import com.google.gerrit.server.permissions.PermissionBackend; 31 : import com.google.gerrit.server.permissions.PermissionBackendException; 32 : import com.google.gerrit.server.permissions.ProjectPermission; 33 : import com.google.gerrit.server.permissions.RefPermission; 34 : import com.google.gerrit.server.plugincontext.PluginSetContext; 35 : import com.google.gerrit.server.plugincontext.PluginSetEntryContext; 36 : import com.google.gerrit.server.project.NoSuchChangeException; 37 : import com.google.gerrit.server.project.ProjectCache; 38 : import com.google.gerrit.server.project.ProjectState; 39 : import com.google.gson.Gson; 40 : import com.google.inject.Inject; 41 : import com.google.inject.Singleton; 42 : import java.util.Optional; 43 : 44 : /** Distributes Events to listeners if they are allowed to see them */ 45 : @Singleton 46 : public class EventBroker implements EventDispatcher { 47 138 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 48 : 49 138 : public static class EventBrokerModule extends LifecycleModule { 50 : @Override 51 : protected void configure() { 52 138 : DynamicItem.itemOf(binder(), EventDispatcher.class); 53 138 : DynamicItem.bind(binder(), EventDispatcher.class).to(EventBroker.class); 54 : 55 138 : bind(Gson.class).annotatedWith(EventGson.class).toProvider(EventGsonProvider.class); 56 138 : } 57 : } 58 : 59 : /** Listeners to receive changes as they happen (limited by visibility of user). */ 60 : protected final PluginSetContext<UserScopedEventListener> listeners; 61 : 62 : /** Listeners to receive all changes as they happen. */ 63 : protected final PluginSetContext<EventListener> unrestrictedListeners; 64 : 65 : private final PermissionBackend permissionBackend; 66 : protected final ProjectCache projectCache; 67 : 68 : protected final ChangeNotes.Factory notesFactory; 69 : 70 : protected final String gerritInstanceId; 71 : 72 : @Inject 73 : public EventBroker( 74 : PluginSetContext<UserScopedEventListener> listeners, 75 : PluginSetContext<EventListener> unrestrictedListeners, 76 : PermissionBackend permissionBackend, 77 : ProjectCache projectCache, 78 : ChangeNotes.Factory notesFactory, 79 138 : @Nullable @GerritInstanceId String gerritInstanceId) { 80 138 : this.listeners = listeners; 81 138 : this.unrestrictedListeners = unrestrictedListeners; 82 138 : this.permissionBackend = permissionBackend; 83 138 : this.projectCache = projectCache; 84 138 : this.notesFactory = notesFactory; 85 138 : this.gerritInstanceId = gerritInstanceId; 86 138 : } 87 : 88 : @Override 89 : public void postEvent(Change change, ChangeEvent event) throws PermissionBackendException { 90 95 : fireEvent(change, event); 91 95 : } 92 : 93 : @Override 94 : public void postEvent(BranchNameKey branchName, RefEvent event) 95 : throws PermissionBackendException { 96 138 : fireEvent(branchName, event); 97 138 : } 98 : 99 : @Override 100 : public void postEvent(Project.NameKey projectName, ProjectEvent event) { 101 135 : fireEvent(projectName, event); 102 135 : } 103 : 104 : @Override 105 : public void postEvent(Event event) throws PermissionBackendException { 106 28 : fireEvent(event); 107 28 : } 108 : 109 : protected void fireEventForUnrestrictedListeners(Event event) { 110 138 : unrestrictedListeners.runEach(l -> l.onEvent(event)); 111 138 : } 112 : 113 : protected void fireEvent(Change change, ChangeEvent event) throws PermissionBackendException { 114 95 : setInstanceIdWhenEmpty(event); 115 95 : for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { 116 92 : CurrentUser user = c.call(UserScopedEventListener::getUser); 117 92 : if (isVisibleTo(change, user)) { 118 92 : c.run(l -> l.onEvent(event)); 119 : } 120 92 : } 121 95 : fireEventForUnrestrictedListeners(event); 122 95 : } 123 : 124 : protected void fireEvent(Project.NameKey project, ProjectEvent event) { 125 135 : setInstanceIdWhenEmpty(event); 126 135 : for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { 127 : 128 40 : CurrentUser user = c.call(UserScopedEventListener::getUser); 129 40 : if (isVisibleTo(project, user)) { 130 40 : c.run(l -> l.onEvent(event)); 131 : } 132 40 : } 133 135 : fireEventForUnrestrictedListeners(event); 134 135 : } 135 : 136 : protected void fireEvent(BranchNameKey branchName, RefEvent event) 137 : throws PermissionBackendException { 138 138 : setInstanceIdWhenEmpty(event); 139 138 : for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { 140 119 : CurrentUser user = c.call(UserScopedEventListener::getUser); 141 119 : if (isVisibleTo(branchName, user)) { 142 115 : c.run(l -> l.onEvent(event)); 143 : } 144 119 : } 145 138 : fireEventForUnrestrictedListeners(event); 146 138 : } 147 : 148 : protected void fireEvent(Event event) throws PermissionBackendException { 149 28 : setInstanceIdWhenEmpty(event); 150 28 : for (PluginSetEntryContext<UserScopedEventListener> c : listeners) { 151 28 : CurrentUser user = c.call(UserScopedEventListener::getUser); 152 28 : if (isVisibleTo(event, user)) { 153 28 : c.run(l -> l.onEvent(event)); 154 : } 155 28 : } 156 28 : fireEventForUnrestrictedListeners(event); 157 28 : } 158 : 159 : protected void setInstanceIdWhenEmpty(Event event) { 160 138 : if (Strings.isNullOrEmpty(event.instanceId)) { 161 138 : event.instanceId = gerritInstanceId; 162 : } 163 138 : } 164 : 165 : protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) { 166 : try { 167 40 : Optional<ProjectState> state = projectCache.get(project); 168 40 : if (!state.isPresent() || !state.get().statePermitsRead()) { 169 0 : return false; 170 : } 171 : 172 40 : return permissionBackend.user(user).project(project).test(ProjectPermission.ACCESS); 173 0 : } catch (PermissionBackendException e) { 174 0 : return false; 175 : } 176 : } 177 : 178 : protected boolean isVisibleTo(Change change, CurrentUser user) throws PermissionBackendException { 179 92 : if (change == null) { 180 0 : return false; 181 : } 182 92 : Optional<ProjectState> pe = projectCache.get(change.getProject()); 183 92 : if (!pe.isPresent() || !pe.get().statePermitsRead()) { 184 0 : return false; 185 : } 186 92 : return permissionBackend 187 92 : .user(user) 188 92 : .change(notesFactory.createChecked(change)) 189 92 : .test(ChangePermission.READ); 190 : } 191 : 192 : protected boolean isVisibleTo(BranchNameKey branchName, CurrentUser user) 193 : throws PermissionBackendException { 194 119 : Optional<ProjectState> pe = projectCache.get(branchName.project()); 195 119 : if (!pe.isPresent() || !pe.get().statePermitsRead()) { 196 2 : return false; 197 : } 198 : 199 119 : return permissionBackend.user(user).ref(branchName).test(RefPermission.READ); 200 : } 201 : 202 : protected boolean isVisibleTo(Event event, CurrentUser user) throws PermissionBackendException { 203 28 : if (event instanceof RefEvent) { 204 26 : RefEvent refEvent = (RefEvent) event; 205 26 : String ref = refEvent.getRefName(); 206 26 : if (PatchSet.isChangeRef(ref)) { 207 0 : Change.Id cid = PatchSet.Id.fromRef(ref).changeId(); 208 : try { 209 0 : Change change = notesFactory.createChecked(refEvent.getProjectNameKey(), cid).getChange(); 210 0 : return isVisibleTo(change, user); 211 0 : } catch (NoSuchChangeException e) { 212 0 : logger.atFine().log( 213 0 : "Change %s cannot be found, falling back on ref visibility check", cid.get()); 214 : } 215 : } 216 26 : return isVisibleTo(refEvent.getBranchNameKey(), user); 217 2 : } else if (event instanceof ProjectEvent) { 218 0 : return isVisibleTo(((ProjectEvent) event).getProjectNameKey(), user); 219 : } 220 2 : return true; 221 : } 222 : }