CHE-10861 Add permissions check for organization related remote subscriptions

6.19.x
Sergii Leshchenko 2018-08-28 16:19:59 +03:00
parent dfb5852da8
commit e62dbb72cb
4 changed files with 270 additions and 3 deletions

View File

@ -24,6 +24,7 @@ import org.eclipse.che.multiuser.organization.api.listener.RemoveOrganizationOnL
import org.eclipse.che.multiuser.organization.api.notification.OrganizationNotificationEmailSender;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationDomain;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationPermissionsFilter;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationRemoteSubscriptionPermissionsChecks;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationResourceDistributionServicePermissionsFilter;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationalAccountPermissionsChecker;
import org.eclipse.che.multiuser.organization.api.resource.DefaultOrganizationResourcesProvider;
@ -43,6 +44,7 @@ public class OrganizationApiModule extends AbstractModule {
protected void configure() {
bind(OrganizationService.class);
bind(OrganizationPermissionsFilter.class);
bind(OrganizationRemoteSubscriptionPermissionsChecks.class);
bind(RemoveOrganizationOnLastUserRemovedEventSubscriber.class).asEagerSingleton();
Multibinder.newSetBinder(binder(), DefaultResourcesProvider.class)

View File

@ -33,9 +33,8 @@ public class OrganizationEventsWebsocketBroadcaster {
private final RemoteSubscriptionManager remoteSubscriptionManager;
private static final String ORGANIZATION_MEMBERSHIP_METHOD_NAME =
"organization/membershipChanged";
private static final String ORGANIZATION_CHANGED_METHOD_NAME = "organization/statusChanged";
public static final String ORGANIZATION_MEMBERSHIP_METHOD_NAME = "organization/membershipChanged";
public static final String ORGANIZATION_CHANGED_METHOD_NAME = "organization/statusChanged";
@Inject
public OrganizationEventsWebsocketBroadcaster(

View File

@ -0,0 +1,110 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.organization.api.permissions;
import static org.eclipse.che.multiuser.organization.api.listener.OrganizationEventsWebsocketBroadcaster.ORGANIZATION_CHANGED_METHOD_NAME;
import static org.eclipse.che.multiuser.organization.api.listener.OrganizationEventsWebsocketBroadcaster.ORGANIZATION_MEMBERSHIP_METHOD_NAME;
import com.google.common.annotations.VisibleForTesting;
import java.util.Map;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.multiuser.api.permission.server.PermissionsManager;
import org.eclipse.che.multiuser.api.permission.server.jsonrpc.RemoteSubscriptionPermissionCheck;
import org.eclipse.che.multiuser.api.permission.server.jsonrpc.RemoteSubscriptionPermissionManager;
import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions;
import org.eclipse.che.multiuser.organization.api.listener.OrganizationEventsWebsocketBroadcaster;
/**
* Holds and registers permissions checks for organization related events.
*
* <p>Covers events published via {@link OrganizationEventsWebsocketBroadcaster}.
*
* @author Sergii Leshchenko
*/
@Singleton
public class OrganizationRemoteSubscriptionPermissionsChecks {
private final PermissionsManager permissionsManager;
@Inject
public OrganizationRemoteSubscriptionPermissionsChecks(PermissionsManager permissionsManager) {
this.permissionsManager = permissionsManager;
}
@Inject
public void register(RemoteSubscriptionPermissionManager permissionFilter) {
MembershipsChangedSubscriptionCheck membershipsEventsCheck =
new MembershipsChangedSubscriptionCheck();
permissionFilter.registerCheck(membershipsEventsCheck, ORGANIZATION_MEMBERSHIP_METHOD_NAME);
OrganizationChangedSubscriptionCheck organizationChangedCheck =
new OrganizationChangedSubscriptionCheck(permissionsManager);
permissionFilter.registerCheck(organizationChangedCheck, ORGANIZATION_CHANGED_METHOD_NAME);
}
@VisibleForTesting
static class MembershipsChangedSubscriptionCheck implements RemoteSubscriptionPermissionCheck {
@Override
public void check(String methodName, Map<String, String> scope) throws ForbiddenException {
String userId = scope.get("userId");
if (userId == null) {
throw new ForbiddenException("User id must be specified in scope");
}
String currentUserId = EnvironmentContext.getCurrent().getSubject().getUserId();
if (!currentUserId.equals(userId)) {
throw new ForbiddenException("It is only allowed to listen to own memberships changes");
}
}
}
@VisibleForTesting
static class OrganizationChangedSubscriptionCheck implements RemoteSubscriptionPermissionCheck {
private final PermissionsManager permissionsManager;
public OrganizationChangedSubscriptionCheck(PermissionsManager permissionsManager) {
this.permissionsManager = permissionsManager;
}
@Override
public void check(String methodName, Map<String, String> scope) throws ForbiddenException {
String organizationId = scope.get("organizationId");
if (organizationId == null) {
throw new ForbiddenException("Organization id must be specified in scope");
}
String currentUserId = EnvironmentContext.getCurrent().getSubject().getUserId();
try {
// check if user has any permissions in organisation
// to listen to related events
AbstractPermissions permissions =
permissionsManager.get(currentUserId, OrganizationDomain.DOMAIN_ID, organizationId);
} catch (ConflictException | ServerException e) {
throw new ForbiddenException("Error occurred while permission fetching: " + e.getMessage());
} catch (NotFoundException e) {
throw new ForbiddenException(
"User doesn't have any permissions for the specified organization");
}
}
}
}

View File

@ -0,0 +1,156 @@
/*
* Copyright (c) 2012-2018 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.multiuser.organization.api.permissions;
import static org.eclipse.che.multiuser.organization.api.listener.OrganizationEventsWebsocketBroadcaster.ORGANIZATION_CHANGED_METHOD_NAME;
import static org.eclipse.che.multiuser.organization.api.listener.OrganizationEventsWebsocketBroadcaster.ORGANIZATION_MEMBERSHIP_METHOD_NAME;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import java.util.Collections;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.multiuser.api.permission.server.PermissionsManager;
import org.eclipse.che.multiuser.api.permission.server.jsonrpc.RemoteSubscriptionPermissionManager;
import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationRemoteSubscriptionPermissionsChecks.MembershipsChangedSubscriptionCheck;
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationRemoteSubscriptionPermissionsChecks.OrganizationChangedSubscriptionCheck;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.testng.MockitoTestNGListener;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;
/**
* Tests {@link OrganizationRemoteSubscriptionPermissionsChecks}.
*
* @author Sergii Leshchenko
*/
@Listeners(MockitoTestNGListener.class)
public class OrganizationRemoteSubscriptionPermissionsChecksTest {
@Mock private Subject subject;
@Mock private PermissionsManager permissionsManager;
@Mock private RemoteSubscriptionPermissionManager permissionManager;
@InjectMocks private OrganizationRemoteSubscriptionPermissionsChecks permissionsChecks;
@BeforeMethod
public void setUp() {
EnvironmentContext.getCurrent().setSubject(subject);
}
@AfterMethod
public void tearDown() {
EnvironmentContext.reset();
}
@Test
public void shouldRegisterChecks() {
// when
permissionsChecks.register(permissionManager);
// then
verify(permissionManager)
.registerCheck(
any(OrganizationChangedSubscriptionCheck.class), eq(ORGANIZATION_CHANGED_METHOD_NAME));
verify(permissionManager)
.registerCheck(
any(MembershipsChangedSubscriptionCheck.class),
eq(ORGANIZATION_MEMBERSHIP_METHOD_NAME));
}
@Test(
expectedExceptions = ForbiddenException.class,
expectedExceptionsMessageRegExp = "User id must be specified in scope")
public void shouldThrowExceptionIfUserIdIsMissing() throws Exception {
// given
MembershipsChangedSubscriptionCheck check = new MembershipsChangedSubscriptionCheck();
when(subject.getUserId()).thenReturn("user2");
// when
check.check(ORGANIZATION_MEMBERSHIP_METHOD_NAME, Collections.emptyMap());
}
@Test(
expectedExceptions = ForbiddenException.class,
expectedExceptionsMessageRegExp = "It is only allowed to listen to own memberships changes")
public void shouldThrowExceptionIfUserTryToListenToForeignMemberships() throws Exception {
// given
MembershipsChangedSubscriptionCheck check = new MembershipsChangedSubscriptionCheck();
when(subject.getUserId()).thenReturn("user2");
// when
check.check(ORGANIZATION_MEMBERSHIP_METHOD_NAME, ImmutableMap.of("userId", "user1"));
}
@Test
public void shouldDoNothingIfUserTryToListenToOwnMemberships() throws Exception {
// given
MembershipsChangedSubscriptionCheck check = new MembershipsChangedSubscriptionCheck();
when(subject.getUserId()).thenReturn("user1");
// when
check.check(ORGANIZATION_MEMBERSHIP_METHOD_NAME, ImmutableMap.of("userId", "user1"));
}
@Test(
expectedExceptions = ForbiddenException.class,
expectedExceptionsMessageRegExp = "Organization id must be specified in scope")
public void shouldThrowExceptionIfOrganizationIdIsMissing() throws Exception {
// given
OrganizationChangedSubscriptionCheck check =
new OrganizationChangedSubscriptionCheck(permissionsManager);
// when
check.check(ORGANIZATION_MEMBERSHIP_METHOD_NAME, Collections.emptyMap());
}
@Test(
expectedExceptions = ForbiddenException.class,
expectedExceptionsMessageRegExp =
"User doesn't have any permissions for the specified organization")
public void shouldThrowExceptionIfUserDoesNotHaveAnyPermissionsToRequestedOrganization()
throws Exception {
// given
OrganizationChangedSubscriptionCheck check =
new OrganizationChangedSubscriptionCheck(permissionsManager);
when(subject.getUserId()).thenReturn("user1");
when(permissionsManager.get("user1", OrganizationDomain.DOMAIN_ID, "org123"))
.thenThrow(new NotFoundException(""));
// when
check.check(ORGANIZATION_MEMBERSHIP_METHOD_NAME, ImmutableMap.of("organizationId", "org123"));
}
@Test
public void shouldDoNothingIfUserTryToListenEventsOfOrganizationWhereHeHasPermissions()
throws Exception {
// given
OrganizationChangedSubscriptionCheck check =
new OrganizationChangedSubscriptionCheck(permissionsManager);
when(subject.getUserId()).thenReturn("user1");
when(permissionsManager.get("user1", OrganizationDomain.DOMAIN_ID, "org123"))
.thenReturn(mock(AbstractPermissions.class));
// when
check.check(ORGANIZATION_MEMBERSHIP_METHOD_NAME, ImmutableMap.of("organizationId", "org123"));
}
}