Limit scope of the machine token signed requests
parent
d7bf4f8d94
commit
01d9fc73da
|
|
@ -62,6 +62,10 @@
|
|||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
|
|
@ -122,6 +126,10 @@
|
|||
<groupId>org.eclipse.persistence</groupId>
|
||||
<artifactId>javax.persistence</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.everrest</groupId>
|
||||
<artifactId>everrest-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public class MachineAuthModule extends AbstractModule {
|
|||
|
||||
bind(MachineTokenProvider.class).to(MachineTokenProviderImpl.class);
|
||||
|
||||
bind(MachineTokenAccessFilter.class);
|
||||
|
||||
bind(SignatureKeyManager.class);
|
||||
bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class);
|
||||
bind(JpaSignatureKeyDao.RemoveKeyPairsBeforeWorkspaceRemovedEventSubscriber.class)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import static com.google.common.base.Strings.isNullOrEmpty;
|
|||
import static java.lang.String.format;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.USER_ID_CLAIM;
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.WORKSPACE_ID_CLAIM;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
|
|
@ -41,7 +42,6 @@ import org.eclipse.che.commons.auth.token.RequestTokenExtractor;
|
|||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.eclipse.che.commons.subject.SubjectImpl;
|
||||
import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
|
||||
|
||||
/**
|
||||
|
|
@ -121,9 +121,9 @@ public class MachineLoginFilter implements Filter {
|
|||
final String userId = claims.get(USER_ID_CLAIM, String.class);
|
||||
// check if user with such id exists
|
||||
final String userName = userManager.getById(userId).getName();
|
||||
|
||||
return new AuthorizedSubject(
|
||||
new SubjectImpl(userName, userId, token, false), permissionChecker);
|
||||
final String workspaceId = claims.get(WORKSPACE_ID_CLAIM, String.class);
|
||||
return new MachineTokenAuthorizedSubject(
|
||||
new SubjectImpl(userName, userId, token, false), permissionChecker, workspaceId);
|
||||
}
|
||||
|
||||
/** Sets given error code with err message into give response. */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.machine.authentication.server;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import javax.ws.rs.Path;
|
||||
import org.eclipse.che.api.core.ForbiddenException;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.everrest.CheMethodInvokerFilter;
|
||||
import org.everrest.core.Filter;
|
||||
import org.everrest.core.resource.GenericResourceMethod;
|
||||
|
||||
/**
|
||||
* Limits set of methods which can be invoked using machine token signed requests.
|
||||
*
|
||||
* @author Max Shaposhnik (mshaposh@redhat.com)
|
||||
*/
|
||||
@Filter
|
||||
@Path("/{path:.*}")
|
||||
public class MachineTokenAccessFilter extends CheMethodInvokerFilter {
|
||||
|
||||
private final SetMultimap<String, String> allowedMethodsByPath = HashMultimap.create();
|
||||
|
||||
public MachineTokenAccessFilter() {
|
||||
allowedMethodsByPath.putAll(
|
||||
"/workspace", asList("getByKey", "addProject", "updateProject", "deleteProject"));
|
||||
allowedMethodsByPath.putAll("/ssh", asList("getPair", "generatePair"));
|
||||
allowedMethodsByPath.putAll(
|
||||
"/factory",
|
||||
asList("getFactoryJson", "getFactory", "getFactoryByAttribute", "resolveFactory"));
|
||||
allowedMethodsByPath.put("/preferences", "find");
|
||||
allowedMethodsByPath.put("/activity", "active");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void filter(GenericResourceMethod genericMethodResource, Object[] arguments)
|
||||
throws ForbiddenException {
|
||||
if (!(EnvironmentContext.getCurrent().getSubject() instanceof MachineTokenAuthorizedSubject)) {
|
||||
return;
|
||||
}
|
||||
if (!allowedMethodsByPath
|
||||
.get(genericMethodResource.getParentResource().getPathValue().getPath())
|
||||
.contains(genericMethodResource.getMethod().getName())) {
|
||||
throw new ForbiddenException("This operation cannot be performed using machine token.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.machine.authentication.server;
|
||||
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
|
||||
import org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain;
|
||||
|
||||
/**
|
||||
* An implementation of {@link Subject} which should be used when request was signed by machine
|
||||
* token. This implementation limits all workspace related permissions to the single workspace for
|
||||
* which the machine token was issued.
|
||||
*
|
||||
* @author Max Shaposhnik (mshaposh@redhat.com)
|
||||
*/
|
||||
public class MachineTokenAuthorizedSubject extends AuthorizedSubject {
|
||||
|
||||
private final String claimsWorkspaceId;
|
||||
|
||||
public MachineTokenAuthorizedSubject(
|
||||
Subject baseSubject, PermissionChecker permissionChecker, String claimsWorkspaceId) {
|
||||
super(baseSubject, permissionChecker);
|
||||
this.claimsWorkspaceId = claimsWorkspaceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String domain, String instance, String action) {
|
||||
if (domain.equals(WorkspaceDomain.DOMAIN_ID) && !instance.equals(claimsWorkspaceId)) {
|
||||
return false;
|
||||
}
|
||||
return super.hasPermission(domain, instance, action);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import static org.mockito.Mockito.when;
|
|||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.impl.DefaultClaims;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -139,6 +140,32 @@ public class MachineLoginFilterTest {
|
|||
+ " JWT validity cannot be asserted and should not be trusted.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotProceedRequestWhenNoWorkspaceIdClaim() throws Exception {
|
||||
final HttpServletRequest requestMock = getRequestMock();
|
||||
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM);
|
||||
kpg.initialize(KEY_SIZE);
|
||||
final KeyPair pair = kpg.generateKeyPair();
|
||||
final Claims badClaims = new DefaultClaims();
|
||||
badClaims.put(Constants.USER_ID_CLAIM, SUBJECT.getUserId());
|
||||
badClaims.put(Claims.ID, "84123-132-fn31");
|
||||
final String token =
|
||||
Jwts.builder()
|
||||
.setClaims(badClaims)
|
||||
.setHeader(HEADER)
|
||||
.signWith(RS512, pair.getPrivate())
|
||||
.compact();
|
||||
when(tokenExtractorMock.getToken(any(HttpServletRequest.class))).thenReturn(token);
|
||||
|
||||
machineLoginFilter.doFilter(requestMock, responseMock, chainMock);
|
||||
|
||||
verify(tokenExtractorMock).getToken(any(HttpServletRequest.class));
|
||||
verify(responseMock)
|
||||
.sendError(
|
||||
401,
|
||||
"Authentication with machine token failed cause: Unable to fetch signature key pair: no workspace id present in token");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProceedRequestWhenEmptyTokenProvided() throws Exception {
|
||||
final HttpServletRequest requestMock = getRequestMock();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.machine.authentication.server;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import org.eclipse.che.api.core.ForbiddenException;
|
||||
import org.eclipse.che.api.workspace.server.WorkspaceService;
|
||||
import org.eclipse.che.commons.env.EnvironmentContext;
|
||||
import org.eclipse.che.multiuser.api.permission.server.AuthorizedSubject;
|
||||
import org.everrest.core.impl.resource.PathValue;
|
||||
import org.everrest.core.resource.GenericResourceMethod;
|
||||
import org.everrest.core.resource.ResourceDescriptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class MachineTokenAccessFilterTest {
|
||||
|
||||
@Mock EnvironmentContext environmentContext;
|
||||
|
||||
@Mock GenericResourceMethod genericMethodResource;
|
||||
|
||||
@Mock MachineTokenAuthorizedSubject machineTokenAuthorizedSubject;
|
||||
|
||||
@Mock AuthorizedSubject authorizedSubject;
|
||||
|
||||
MachineTokenAccessFilter filter;
|
||||
|
||||
@BeforeMethod
|
||||
private void setUp() {
|
||||
filter = new MachineTokenAccessFilter();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotLimitAccessIfSubjectIsNotMachineAuthorized() throws Exception {
|
||||
when(environmentContext.getSubject()).thenReturn(authorizedSubject);
|
||||
EnvironmentContext.setCurrent(environmentContext);
|
||||
filter.filter(genericMethodResource, new Object[] {});
|
||||
verifyZeroInteractions(genericMethodResource);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = ForbiddenException.class)
|
||||
public void shouldLimitAccessIfMethodIsNotAllowed() throws Exception {
|
||||
when(environmentContext.getSubject()).thenReturn(machineTokenAuthorizedSubject);
|
||||
EnvironmentContext.setCurrent(environmentContext);
|
||||
Method method = WorkspaceService.class.getMethod("getServiceDescriptor");
|
||||
ResourceDescriptor descriptor = mock(ResourceDescriptor.class);
|
||||
PathValue pathValue = mock(PathValue.class);
|
||||
|
||||
when(genericMethodResource.getMethod()).thenReturn(method);
|
||||
when(descriptor.getPathValue()).thenReturn(pathValue);
|
||||
when(genericMethodResource.getParentResource()).thenReturn(descriptor);
|
||||
when(pathValue.getPath()).thenReturn("/workspace");
|
||||
|
||||
filter.filter(genericMethodResource, new Object[] {});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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.machine.authentication.server;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
|
||||
import org.eclipse.che.commons.subject.Subject;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
|
||||
import org.eclipse.che.multiuser.api.permission.server.SystemDomain;
|
||||
import org.eclipse.che.multiuser.permission.workspace.server.WorkspaceDomain;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class MachineTokenAuthorizedSubjectTest {
|
||||
|
||||
private static final String WS_ID = "ws123";
|
||||
private static final String USER_ID = "user123";
|
||||
@Mock private PermissionChecker permissionChecker;
|
||||
|
||||
@Mock Subject baseSubject;
|
||||
|
||||
private MachineTokenAuthorizedSubject subject;
|
||||
|
||||
@BeforeMethod
|
||||
private void setUp() {
|
||||
when(baseSubject.getUserId()).thenReturn(USER_ID);
|
||||
subject = new MachineTokenAuthorizedSubject(baseSubject, permissionChecker, WS_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRejectPermissionsFromAnotherWorkspace() {
|
||||
assertFalse(
|
||||
subject.hasPermission(WorkspaceDomain.DOMAIN_ID, "another_ws", WorkspaceDomain.READ));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRequestPermissionsFromBaseSubjectForNonWorkspaceDomains() throws Exception {
|
||||
subject.hasPermission(SystemDomain.DOMAIN_ID, "", SystemDomain.MANAGE_SYSTEM_ACTION);
|
||||
verify(permissionChecker, atLeastOnce())
|
||||
.hasPermission(
|
||||
eq(USER_ID), eq(SystemDomain.DOMAIN_ID), eq(""), eq(SystemDomain.MANAGE_SYSTEM_ACTION));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue