Make machine token signing key per-workspace & renew them after each ws restart
parent
6bc44e9512
commit
47b8ed328b
|
|
@ -43,6 +43,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.model.workspace.config.MachineConfig;
|
||||
import org.eclipse.che.api.core.model.workspace.config.ServerConfig;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
|
|
@ -208,10 +209,13 @@ public class JwtProxyProvisioner {
|
|||
k8sEnv.getMachines().put(JWT_PROXY_MACHINE_NAME, createJwtProxyMachine());
|
||||
k8sEnv.getPods().put(JWT_PROXY_POD_NAME, createJwtProxyPod(identity));
|
||||
|
||||
KeyPair keyPair = signatureKeyManager.getKeyPair();
|
||||
if (keyPair == null) {
|
||||
KeyPair keyPair;
|
||||
try {
|
||||
keyPair = signatureKeyManager.getKeyPair(identity.getWorkspaceId());
|
||||
} catch (ServerException e) {
|
||||
throw new InternalInfrastructureException(
|
||||
"Key pair for machine authentication does not exist");
|
||||
"Signature key pair for machine authentication cannot be retrieved. Reason: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
Map<String, String> initConfigMapData = new HashMap<>();
|
||||
initConfigMapData.put(
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.
|
|||
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.PUBLIC_KEY_FOOTER;
|
||||
import static org.eclipse.che.workspace.infrastructure.kubernetes.server.secure.jwtproxy.JwtProxyProvisioner.PUBLIC_KEY_HEADER;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
|
@ -73,8 +74,8 @@ public class JwtProxyProvisionerTest {
|
|||
private KubernetesEnvironment k8sEnv;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() {
|
||||
when(signatureKeyManager.getKeyPair()).thenReturn(new KeyPair(publicKey, null));
|
||||
public void setUp() throws Exception {
|
||||
when(signatureKeyManager.getKeyPair(anyString())).thenReturn(new KeyPair(publicKey, null));
|
||||
when(publicKey.getEncoded()).thenReturn("publickey".getBytes());
|
||||
|
||||
when(configBuilderFactory.create(any()))
|
||||
|
|
|
|||
|
|
@ -138,6 +138,16 @@
|
|||
<artifactId>che-core-sql-schema</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-api-authorization</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-api-authorization-impl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-api-organization</artifactId>
|
||||
|
|
@ -158,6 +168,11 @@
|
|||
<artifactId>che-multiuser-api-resource</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-machine-authentication</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-workspace</artifactId>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjec
|
|||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createFreeResourcesLimit;
|
||||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createPreferences;
|
||||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createProfile;
|
||||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createSignatureKeyPair;
|
||||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createSshPair;
|
||||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createStack;
|
||||
import static org.eclipse.che.multiuser.integration.jpa.cascaderemoval.TestObjectsFactory.createUser;
|
||||
|
|
@ -44,6 +45,7 @@ import com.google.inject.multibindings.MapBinder;
|
|||
import com.google.inject.multibindings.Multibinder;
|
||||
import com.google.inject.name.Names;
|
||||
import com.google.inject.persist.jpa.JpaPersistModule;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
|
@ -95,8 +97,13 @@ import org.eclipse.che.core.db.cascade.event.CascadeEvent;
|
|||
import org.eclipse.che.core.db.schema.SchemaInitializer;
|
||||
import org.eclipse.che.core.db.schema.impl.flyway.FlywaySchemaInitializer;
|
||||
import org.eclipse.che.inject.lifecycle.InitModule;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionChecker;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionCheckerImpl;
|
||||
import org.eclipse.che.multiuser.api.permission.server.PermissionsManager;
|
||||
import org.eclipse.che.multiuser.api.permission.server.model.impl.AbstractPermissions;
|
||||
import org.eclipse.che.multiuser.api.permission.server.spi.PermissionsDao;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.MachineAuthModule;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao;
|
||||
import org.eclipse.che.multiuser.organization.api.OrganizationJpaModule;
|
||||
import org.eclipse.che.multiuser.organization.api.OrganizationManager;
|
||||
import org.eclipse.che.multiuser.organization.api.listener.RemoveOrganizationOnLastUserRemovedEventSubscriber;
|
||||
|
|
@ -147,6 +154,7 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
private FactoryDao factoryDao;
|
||||
private StackDao stackDao;
|
||||
private WorkerDao workerDao;
|
||||
private SignatureKeyDao signatureKeyDao;
|
||||
private JpaStackPermissionsDao stackPermissionsDao;
|
||||
private FreeResourcesLimitDao freeResourcesLimitDao;
|
||||
private OrganizationManager organizationManager;
|
||||
|
|
@ -229,6 +237,7 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
install(new FactoryJpaModule());
|
||||
install(new OrganizationJpaModule());
|
||||
install(new MultiuserWorkspaceJpaModule());
|
||||
install(new MachineAuthModule());
|
||||
|
||||
bind(FreeResourcesLimitDao.class).to(JpaFreeResourcesLimitDao.class);
|
||||
bind(RemoveFreeResourcesLimitSubscriber.class).asEagerSingleton();
|
||||
|
|
@ -237,6 +246,8 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
bind(WorkspaceStatusCache.class).to(DefaultWorkspaceStatusCache.class);
|
||||
bind(RuntimeInfrastructure.class).toInstance(mock(RuntimeInfrastructure.class));
|
||||
MapBinder.newMapBinder(binder(), String.class, InternalEnvironmentFactory.class);
|
||||
bind(PermissionsManager.class);
|
||||
bind(PermissionChecker.class).to(PermissionCheckerImpl.class);
|
||||
bind(AccountManager.class);
|
||||
bind(Boolean.class)
|
||||
.annotatedWith(Names.named("che.workspace.auto_snapshot"))
|
||||
|
|
@ -293,6 +304,7 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
factoryDao = injector.getInstance(FactoryDao.class);
|
||||
stackDao = injector.getInstance(StackDao.class);
|
||||
workerDao = injector.getInstance(WorkerDao.class);
|
||||
signatureKeyDao = injector.getInstance(SignatureKeyDao.class);
|
||||
freeResourcesLimitDao = injector.getInstance(FreeResourcesLimitDao.class);
|
||||
organizationManager = injector.getInstance(OrganizationManager.class);
|
||||
memberDao = injector.getInstance(MemberDao.class);
|
||||
|
|
@ -361,6 +373,10 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
assertNull(notFoundToNull(() -> freeResourcesLimitDao.get(user.getId())));
|
||||
assertNull(notFoundToNull(() -> freeResourcesLimitDao.get(user2.getId())));
|
||||
|
||||
// machine token keypairs
|
||||
assertNull(notFoundToNull(() -> signatureKeyDao.get(workspace1.getId())));
|
||||
assertNull(notFoundToNull(() -> signatureKeyDao.get(workspace2.getId())));
|
||||
|
||||
// distributed resources is removed
|
||||
assertNull(
|
||||
notFoundToNull(() -> organizationResourcesDistributor.get(childOrganization.getId())));
|
||||
|
|
@ -395,6 +411,7 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
assertNotNull(notFoundToNull(() -> organizationManager.getById(organization.getId())));
|
||||
assertNotNull(notFoundToNull(() -> organizationManager.getById(childOrganization.getId())));
|
||||
assertNotNull(notFoundToNull(() -> organizationManager.getById(organization2.getId())));
|
||||
assertNotNull(notFoundToNull(() -> signatureKeyDao.get(workspace2.getId())));
|
||||
assertFalse(
|
||||
organizationResourcesDistributor.getResourcesCaps(childOrganization.getId()).isEmpty());
|
||||
wipeTestData();
|
||||
|
|
@ -408,7 +425,8 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
};
|
||||
}
|
||||
|
||||
private void createTestData() throws NotFoundException, ConflictException, ServerException {
|
||||
private void createTestData()
|
||||
throws NotFoundException, ConflictException, ServerException, NoSuchAlgorithmException {
|
||||
userDao.create(user = createUser("bobby"));
|
||||
accountDao.create(account = createAccount("bobby"));
|
||||
// test permissions users
|
||||
|
|
@ -435,6 +453,9 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
|
||||
workerDao.store(createWorker(user2.getId(), workspace3.getId()));
|
||||
|
||||
signatureKeyDao.create(createSignatureKeyPair(workspace1.getId()));
|
||||
signatureKeyDao.create(createSignatureKeyPair(workspace2.getId()));
|
||||
|
||||
stackPermissionsDao.store(
|
||||
new StackPermissionsImpl(
|
||||
user2.getId(), stack1.getId(), asList(SET_PERMISSIONS, "read", "write")));
|
||||
|
|
@ -511,6 +532,9 @@ public class JpaEntitiesCascadeRemovalTest {
|
|||
sshDao.remove(sshPair1.getOwner(), sshPair1.getService(), sshPair1.getName());
|
||||
sshDao.remove(sshPair2.getOwner(), sshPair2.getService(), sshPair2.getName());
|
||||
|
||||
signatureKeyDao.remove(workspace1.getId());
|
||||
signatureKeyDao.remove(workspace2.getId());
|
||||
|
||||
workspaceDao.remove(workspace1.getId());
|
||||
workspaceDao.remove(workspace2.getId());
|
||||
workspaceDao.remove(workspace3.getId());
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ package org.eclipse.che.multiuser.integration.jpa.cascaderemoval;
|
|||
import static java.util.Arrays.asList;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
@ -29,6 +32,7 @@ import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
|||
import org.eclipse.che.api.workspace.server.model.impl.stack.StackComponentImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl;
|
||||
import org.eclipse.che.api.workspace.server.stack.image.StackIcon;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl;
|
||||
import org.eclipse.che.multiuser.permission.workspace.server.model.impl.WorkerImpl;
|
||||
import org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl;
|
||||
import org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl;
|
||||
|
|
@ -120,5 +124,14 @@ public final class TestObjectsFactory {
|
|||
Arrays.asList(new ResourceImpl("test1", 123, "mb"), new ResourceImpl("test2", 234, "h")));
|
||||
}
|
||||
|
||||
public static SignatureKeyPairImpl createSignatureKeyPair(String workspaceId)
|
||||
throws NoSuchAlgorithmException {
|
||||
final KeyPairGenerator kpg;
|
||||
kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(512);
|
||||
final KeyPair pair = kpg.generateKeyPair();
|
||||
return new SignatureKeyPairImpl(workspaceId, pair.getPublic(), pair.getPrivate());
|
||||
}
|
||||
|
||||
private TestObjectsFactory() {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,9 @@
|
|||
<class>org.eclipse.che.multiuser.organization.spi.impl.MemberImpl</class>
|
||||
<class>org.eclipse.che.multiuser.organization.spi.impl.OrganizationDistributedResourcesImpl</class>
|
||||
|
||||
<class>org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyImpl</class>
|
||||
<class>org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl</class>
|
||||
|
||||
<class>org.eclipse.che.multiuser.resource.spi.impl.ResourceImpl</class>
|
||||
<class>org.eclipse.che.multiuser.resource.spi.impl.FreeResourcesLimitImpl</class>
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
|
|
|
|||
|
|
@ -102,6 +102,11 @@
|
|||
<artifactId>che-multiuser-api-permission</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-machine-authentication</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.multiuser</groupId>
|
||||
<artifactId>che-multiuser-permission-workspace</artifactId>
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@ import org.eclipse.che.multiuser.api.permission.server.jpa.JpaSystemPermissionsD
|
|||
import org.eclipse.che.multiuser.api.permission.server.model.impl.SystemPermissionsImpl;
|
||||
import org.eclipse.che.multiuser.api.permission.server.spi.PermissionsDao;
|
||||
import org.eclipse.che.multiuser.api.permission.server.spi.tck.SystemPermissionsDaoTest;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.jpa.JpaSignatureKeyDao;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao;
|
||||
import org.eclipse.che.multiuser.organization.api.permissions.OrganizationDomain;
|
||||
import org.eclipse.che.multiuser.organization.spi.MemberDao;
|
||||
import org.eclipse.che.multiuser.organization.spi.OrganizationDao;
|
||||
|
|
@ -163,6 +166,10 @@ public class MultiuserPostgresqlTckModule extends TckModule {
|
|||
bind(new TypeLiteral<TckRepository<FreeResourcesLimitImpl>>() {})
|
||||
.toInstance(new JpaTckRepository<>(FreeResourcesLimitImpl.class));
|
||||
|
||||
// machine token keys
|
||||
bind(new TypeLiteral<TckRepository<SignatureKeyPairImpl>>() {})
|
||||
.toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class));
|
||||
|
||||
// dao
|
||||
bind(OrganizationDao.class).to(JpaOrganizationDao.class);
|
||||
bind(OrganizationDistributedResourcesDao.class)
|
||||
|
|
@ -171,6 +178,7 @@ public class MultiuserPostgresqlTckModule extends TckModule {
|
|||
|
||||
bind(WorkerDao.class).to(JpaWorkerDao.class);
|
||||
bind(MemberDao.class).to(JpaMemberDao.class);
|
||||
bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class);
|
||||
bind(new TypeLiteral<PermissionsDao<MemberImpl>>() {}).to(JpaMemberDao.class);
|
||||
bind(new TypeLiteral<AbstractPermissionsDomain<MemberImpl>>() {}).to(OrganizationDomain.class);
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,10 @@
|
|||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-auth</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-inject</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-commons-lang</artifactId>
|
||||
|
|
|
|||
|
|
@ -11,52 +11,38 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.keycloak.server;
|
||||
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwt;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import java.security.PublicKey;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
|
||||
|
||||
/**
|
||||
* Base abstract class for the Keycloak-related servlet filters.
|
||||
*
|
||||
* <p>In particular it defines commnon use-cases when the authentication / multi-user logic should
|
||||
* be skipped
|
||||
* <p>In particular it defines common use-cases when the authentication / multi-user logic should be
|
||||
* skipped
|
||||
*/
|
||||
public abstract class AbstractKeycloakFilter implements Filter {
|
||||
|
||||
@Inject protected SignatureKeyManager signatureKeyManager;
|
||||
@Inject protected JwtParser jwtParser;
|
||||
|
||||
/** when a request came from a machine with valid token then auth is not required */
|
||||
protected boolean shouldSkipAuthentication(HttpServletRequest request, String token) {
|
||||
boolean shouldSkipAuthentication(HttpServletRequest request, String token) {
|
||||
if (token == null) {
|
||||
if (request.getRequestURI() != null
|
||||
&& request.getRequestURI().endsWith("api/keycloak/OIDCKeycloak.js")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return request.getRequestURI() != null
|
||||
&& request.getRequestURI().endsWith("api/keycloak/OIDCKeycloak.js");
|
||||
}
|
||||
try {
|
||||
final PublicKey publicKey = signatureKeyManager.getKeyPair().getPublic();
|
||||
final Jwt jwt = Jwts.parser().setSigningKey(publicKey).parse(token);
|
||||
return MACHINE_TOKEN_KIND.equals(jwt.getHeader().get("kind"));
|
||||
} catch (ExpiredJwtException | MalformedJwtException | SignatureException ex) {
|
||||
// given token is not signed by particular signature key so it must be checked in another way
|
||||
jwtParser.parse(token);
|
||||
return false;
|
||||
} catch (MachineTokenJwtException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {}
|
||||
public void init(FilterConfig filterConfig) {}
|
||||
|
||||
@Override
|
||||
public void destroy() {}
|
||||
|
|
|
|||
|
|
@ -11,28 +11,12 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.keycloak.server;
|
||||
|
||||
import com.auth0.jwk.GuavaCachedJwkProvider;
|
||||
import com.auth0.jwk.Jwk;
|
||||
import com.auth0.jwk.JwkException;
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import com.auth0.jwk.UrlJwkProvider;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.Key;
|
||||
import java.security.PublicKey;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
|
|
@ -41,31 +25,19 @@ import javax.servlet.ServletResponse;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.che.commons.auth.token.RequestTokenExtractor;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Singleton
|
||||
public class KeycloakAuthenticationFilter extends AbstractKeycloakFilter {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KeycloakAuthenticationFilter.class);
|
||||
|
||||
private String jwksUrl;
|
||||
private long allowedClockSkewSec;
|
||||
private RequestTokenExtractor tokenExtractor;
|
||||
private JwkProvider jwkProvider;
|
||||
|
||||
@Inject
|
||||
public KeycloakAuthenticationFilter(
|
||||
KeycloakSettings keycloakSettings,
|
||||
@Named(KeycloakConstants.ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec,
|
||||
RequestTokenExtractor tokenExtractor)
|
||||
throws MalformedURLException {
|
||||
this.jwksUrl = keycloakSettings.get().get(KeycloakConstants.JWKS_ENDPOINT_SETTING);
|
||||
this.allowedClockSkewSec = allowedClockSkewSec;
|
||||
public KeycloakAuthenticationFilter(RequestTokenExtractor tokenExtractor) {
|
||||
this.tokenExtractor = tokenExtractor;
|
||||
if (jwksUrl != null) {
|
||||
this.jwkProvider = new GuavaCachedJwkProvider(new UrlJwkProvider(new URL(jwksUrl)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -74,80 +46,34 @@ public class KeycloakAuthenticationFilter extends AbstractKeycloakFilter {
|
|||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
|
||||
final String token = tokenExtractor.getToken(request);
|
||||
if (shouldSkipAuthentication(request, token)) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (token == null) {
|
||||
send403(res, "Authorization token is missed");
|
||||
send401(res, "Authorization token is missed");
|
||||
return;
|
||||
}
|
||||
|
||||
Jws<Claims> jwt;
|
||||
try {
|
||||
jwt =
|
||||
Jwts.parser()
|
||||
.setAllowedClockSkewSeconds(allowedClockSkewSec)
|
||||
.setSigningKeyResolver(
|
||||
new SigningKeyResolverAdapter() {
|
||||
@Override
|
||||
public Key resolveSigningKey(
|
||||
@SuppressWarnings("rawtypes") JwsHeader header, Claims claims) {
|
||||
try {
|
||||
return getJwtPublicKey(header);
|
||||
} catch (JwkException e) {
|
||||
throw new JwtException(
|
||||
"Error during the retrieval of the public key during JWT token validation",
|
||||
e);
|
||||
}
|
||||
}
|
||||
})
|
||||
.parseClaimsJws(token);
|
||||
if (shouldSkipAuthentication(request, token)) {
|
||||
chain.doFilter(req, res);
|
||||
return;
|
||||
}
|
||||
jwt = jwtParser.parseClaimsJws(token);
|
||||
LOG.debug("JWT = ", jwt);
|
||||
// OK, we can trust this JWT
|
||||
} catch (SignatureException
|
||||
| IllegalArgumentException
|
||||
| MalformedJwtException
|
||||
| UnsupportedJwtException e) {
|
||||
send403(res, "The specified token is not a valid. " + e.getMessage());
|
||||
return;
|
||||
} catch (ExpiredJwtException e) {
|
||||
send403(res, "The specified token is expired");
|
||||
send401(res, "The specified token is expired");
|
||||
return;
|
||||
} catch (JwtException e) {
|
||||
send401(res, "Token validation failed: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
request.setAttribute("token", jwt);
|
||||
chain.doFilter(req, res);
|
||||
}
|
||||
|
||||
private synchronized PublicKey getJwtPublicKey(JwsHeader<?> header) throws JwkException {
|
||||
String kid = header.getKeyId();
|
||||
if (kid == null) {
|
||||
LOG.warn(
|
||||
"'kid' is missing in the JWT token header. This is not possible to validate the token with OIDC provider keys");
|
||||
return null;
|
||||
}
|
||||
String alg = header.getAlgorithm();
|
||||
if (alg == null) {
|
||||
LOG.warn(
|
||||
"'alg' is missing in the JWT token header. This is not possible to validate the token with OIDC provider keys");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (jwkProvider == null) {
|
||||
LOG.warn(
|
||||
"JWK provider is not available: This is not possible to validate the token with OIDC provider keys.\n"
|
||||
+ "Please look into the startup logs to find out the root cause");
|
||||
return null;
|
||||
}
|
||||
Jwk jwk = jwkProvider.get(kid);
|
||||
return jwk.getPublicKey();
|
||||
}
|
||||
|
||||
private void send403(ServletResponse res, String message) throws IOException {
|
||||
private void send401(ServletResponse res, String message) throws IOException {
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
response.getOutputStream().write(message.getBytes());
|
||||
response.setStatus(403);
|
||||
response.setStatus(401);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
|
|||
public class KeycloakEnvironmentInitalizationFilter extends AbstractKeycloakFilter {
|
||||
|
||||
private final KeycloakUserManager userManager;
|
||||
private final KeycloakSettings settings;
|
||||
private final RequestTokenExtractor tokenExtractor;
|
||||
private final PermissionChecker permissionChecker;
|
||||
private final KeycloakSettings keycloakSettings;
|
||||
|
||||
@Inject
|
||||
public KeycloakEnvironmentInitalizationFilter(
|
||||
|
|
@ -57,7 +57,7 @@ public class KeycloakEnvironmentInitalizationFilter extends AbstractKeycloakFilt
|
|||
this.userManager = userManager;
|
||||
this.tokenExtractor = tokenExtractor;
|
||||
this.permissionChecker = permissionChecker;
|
||||
this.settings = settings;
|
||||
this.keycloakSettings = settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -82,7 +82,8 @@ public class KeycloakEnvironmentInitalizationFilter extends AbstractKeycloakFilt
|
|||
|
||||
try {
|
||||
String username =
|
||||
claims.get(settings.get().get(KeycloakConstants.USERNAME_CLAIM_SETTING), String.class);
|
||||
claims.get(
|
||||
keycloakSettings.get().get(KeycloakConstants.USERNAME_CLAIM_SETTING), String.class);
|
||||
if (username == null) { // fallback to unique id promised by spec
|
||||
// https://openid.net/specs/openid-connect-basic-1_0.html#ClaimStability
|
||||
username = claims.getIssuer() + ":" + claims.getSubject();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.keycloak.server;
|
||||
|
||||
import com.auth0.jwk.GuavaCachedJwkProvider;
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import com.auth0.jwk.UrlJwkProvider;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import org.eclipse.che.inject.ConfigurationException;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
|
||||
|
||||
/** Constructs {@link UrlJwkProvider} based on Jwk endpoint from keycloak settings */
|
||||
public class KeycloakJwkProvider implements Provider<JwkProvider> {
|
||||
|
||||
private final JwkProvider jwkProvider;
|
||||
|
||||
@Inject
|
||||
public KeycloakJwkProvider(KeycloakSettings keycloakSettings) throws MalformedURLException {
|
||||
final String jwksUrl = keycloakSettings.get().get(KeycloakConstants.JWKS_ENDPOINT_SETTING);
|
||||
if (jwksUrl == null) {
|
||||
throw new ConfigurationException("Jwks endpoint url not found in keycloak settings");
|
||||
}
|
||||
this.jwkProvider = new GuavaCachedJwkProvider(new UrlJwkProvider(new URL(jwksUrl)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwkProvider get() {
|
||||
return jwkProvider;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.keycloak.server;
|
||||
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants;
|
||||
|
||||
/** Provides instance of {@link JwtParser} */
|
||||
@Singleton
|
||||
public class KeycloakJwtParserProvider implements Provider<JwtParser> {
|
||||
|
||||
private final JwtParser jwtParser;
|
||||
|
||||
@Inject
|
||||
public KeycloakJwtParserProvider(
|
||||
@Named(KeycloakConstants.ALLOWED_CLOCK_SKEW_SEC) long allowedClockSkewSec,
|
||||
KeycloakSigningKeyResolver keycloakSigningKeyResolver) {
|
||||
this.jwtParser =
|
||||
Jwts.parser()
|
||||
.setAllowedClockSkewSeconds(allowedClockSkewSec)
|
||||
.setSigningKeyResolver(keycloakSigningKeyResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtParser get() {
|
||||
return jwtParser;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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.keycloak.server;
|
||||
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;
|
||||
|
||||
import com.auth0.jwk.JwkException;
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||
import java.security.Key;
|
||||
import java.security.PublicKey;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Resolves signing key based on id from JWT header */
|
||||
@Singleton
|
||||
public class KeycloakSigningKeyResolver extends SigningKeyResolverAdapter {
|
||||
|
||||
private final JwkProvider jwkProvider;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KeycloakSigningKeyResolver.class);
|
||||
|
||||
@Inject
|
||||
KeycloakSigningKeyResolver(JwkProvider jwkProvider) {
|
||||
this.jwkProvider = jwkProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key resolveSigningKey(JwsHeader header, String plaintext) {
|
||||
if (MACHINE_TOKEN_KIND.equals(header.get("kind"))) {
|
||||
throw new MachineTokenJwtException(); // machine token, doesn't need to verify
|
||||
}
|
||||
return getJwtPublicKey(header);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
if (MACHINE_TOKEN_KIND.equals(header.get("kind"))) {
|
||||
throw new MachineTokenJwtException(); // machine token, doesn't need to verify
|
||||
}
|
||||
return getJwtPublicKey(header);
|
||||
}
|
||||
|
||||
private synchronized PublicKey getJwtPublicKey(JwsHeader<?> header) {
|
||||
String kid = header.getKeyId();
|
||||
if (header.getKeyId() == null) {
|
||||
LOG.warn(
|
||||
"'kid' is missing in the JWT token header. This is not possible to validate the token with OIDC provider keys");
|
||||
throw new JwtException("'kid' is missing in the JWT token header.");
|
||||
}
|
||||
try {
|
||||
return jwkProvider.get(kid).getPublicKey();
|
||||
} catch (JwkException e) {
|
||||
throw new JwtException(
|
||||
"Error during the retrieval of the public key during JWT token validation", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.keycloak.server;
|
||||
|
||||
import io.jsonwebtoken.JwtException;
|
||||
|
||||
public class MachineTokenJwtException extends JwtException {
|
||||
|
||||
public MachineTokenJwtException() {
|
||||
super("This is a machine token");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,12 +11,16 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.keycloak.server.deploy;
|
||||
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import com.google.inject.AbstractModule;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
|
||||
import org.eclipse.che.api.user.server.TokenValidator;
|
||||
import org.eclipse.che.api.user.server.spi.ProfileDao;
|
||||
import org.eclipse.che.multiuser.api.account.personal.PersonalAccountUserManager;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakConfigurationService;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakJwkProvider;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakJwtParserProvider;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakTokenValidator;
|
||||
import org.eclipse.che.multiuser.keycloak.server.KeycloakUserManager;
|
||||
import org.eclipse.che.multiuser.keycloak.server.dao.KeycloakProfileDao;
|
||||
|
|
@ -32,6 +36,8 @@ public class KeycloakModule extends AbstractModule {
|
|||
bind(KeycloakConfigurationService.class);
|
||||
|
||||
bind(ProfileDao.class).to(KeycloakProfileDao.class);
|
||||
bind(JwkProvider.class).toProvider(KeycloakJwkProvider.class);
|
||||
bind(JwtParser.class).toProvider(KeycloakJwtParserProvider.class);
|
||||
bind(PersonalAccountUserManager.class).to(KeycloakUserManager.class);
|
||||
|
||||
bind(OAuthAPI.class).toProvider(OAuthAPIProvider.class);
|
||||
|
|
|
|||
|
|
@ -11,26 +11,21 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.keycloak.server;
|
||||
|
||||
import static io.jsonwebtoken.SignatureAlgorithm.RS256;
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import io.jsonwebtoken.Jwt;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Listeners;
|
||||
|
|
@ -45,27 +40,12 @@ import org.testng.annotations.Test;
|
|||
public class AbstractKeycloakFilterTest {
|
||||
|
||||
@Mock private HttpServletRequest request;
|
||||
@Mock private SignatureKeyManager signatureKeyManager;
|
||||
@Mock private JwtParser jwtParser;
|
||||
|
||||
@InjectMocks private TestLoginFilter abstractKeycloakFilter;
|
||||
|
||||
private String machineToken;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() throws Exception {
|
||||
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(512);
|
||||
final KeyPair keyPair = kpg.generateKeyPair();
|
||||
final Map<String, Object> header = new HashMap<>();
|
||||
header.put("kind", MACHINE_TOKEN_KIND);
|
||||
machineToken =
|
||||
Jwts.builder()
|
||||
.setPayload("payload")
|
||||
.setHeader(header)
|
||||
.signWith(RS256, keyPair.getPrivate())
|
||||
.compact();
|
||||
|
||||
when(signatureKeyManager.getKeyPair()).thenReturn(keyPair);
|
||||
public void setup() {
|
||||
when(request.getRequestURI()).thenReturn(null);
|
||||
}
|
||||
|
||||
|
|
@ -82,18 +62,23 @@ public class AbstractKeycloakFilterTest {
|
|||
|
||||
@Test
|
||||
public void testShouldNotSkipAuthWhenProvidedTokenIsNotMachine() {
|
||||
assertFalse(abstractKeycloakFilter.shouldSkipAuthentication(request, "testToken"));
|
||||
Jwt mock = Mockito.mock(Jwt.class);
|
||||
doReturn(mock).when(jwtParser).parse(anyString());
|
||||
assertFalse(abstractKeycloakFilter.shouldSkipAuthentication(request, "token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthIsNotNeededWhenMachineTokenProvided() throws Exception {
|
||||
assertTrue(abstractKeycloakFilter.shouldSkipAuthentication(request, machineToken));
|
||||
public void testAuthIsNotNeededWhenMachineTokenProvided() {
|
||||
when(jwtParser.parse(anyString())).thenThrow(MachineTokenJwtException.class);
|
||||
assertTrue(abstractKeycloakFilter.shouldSkipAuthentication(request, "token"));
|
||||
}
|
||||
|
||||
static class TestLoginFilter extends AbstractKeycloakFilter {
|
||||
|
||||
public TestLoginFilter() {}
|
||||
|
||||
@Override
|
||||
public void doFilter(
|
||||
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException {}
|
||||
ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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.keycloak.server;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import java.lang.reflect.Field;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.che.commons.auth.token.RequestTokenExtractor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
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 KeycloakAuthenticationFilterTest {
|
||||
|
||||
@Mock private RequestTokenExtractor tokenExtractor;
|
||||
@Mock private JwtParser jwtParser;
|
||||
@Mock private ServletOutputStream servletOutputStream;
|
||||
@Mock private HttpServletRequest request;
|
||||
@Mock private HttpServletResponse response;
|
||||
@Mock private FilterChain chain;
|
||||
|
||||
private KeycloakAuthenticationFilter authenticationFilter;
|
||||
|
||||
@BeforeMethod
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
authenticationFilter = new KeycloakAuthenticationFilter(tokenExtractor);
|
||||
Field parser = authenticationFilter.getClass().getSuperclass().getDeclaredField("jwtParser");
|
||||
parser.setAccessible(true);
|
||||
parser.set(authenticationFilter, jwtParser);
|
||||
when(response.getOutputStream()).thenReturn(servletOutputStream);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSend401IfNoTokenInRequest() throws Exception {
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(null);
|
||||
|
||||
authenticationFilter.doFilter(request, response, chain);
|
||||
|
||||
verify(response).setStatus(401);
|
||||
verify(servletOutputStream).write(eq("Authorization token is missed".getBytes()));
|
||||
verifyNoMoreInteractions(chain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSend401IfTokenIsExpired() throws Exception {
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
|
||||
when(jwtParser.parse(anyString())).thenThrow(ExpiredJwtException.class);
|
||||
|
||||
authenticationFilter.doFilter(request, response, chain);
|
||||
|
||||
verify(response).setStatus(401);
|
||||
verify(servletOutputStream).write(eq("The specified token is expired".getBytes()));
|
||||
verifyNoMoreInteractions(chain);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSend401IfTokenIsCheckSignatureFailed() throws Exception {
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token");
|
||||
when(jwtParser.parse(anyString())).thenThrow(new JwtException("bad signature"));
|
||||
|
||||
authenticationFilter.doFilter(request, response, chain);
|
||||
|
||||
verify(response).setStatus(401);
|
||||
verify(servletOutputStream).write(eq("Token validation failed: bad signature".getBytes()));
|
||||
verifyNoMoreInteractions(chain);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.keycloak.server;
|
||||
|
||||
import static io.jsonwebtoken.SignatureAlgorithm.RS512;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
|
@ -23,12 +22,12 @@ import static org.mockito.Mockito.when;
|
|||
import static org.testng.AssertJUnit.assertEquals;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.impl.DefaultClaims;
|
||||
import io.jsonwebtoken.impl.DefaultHeader;
|
||||
import io.jsonwebtoken.impl.DefaultJwt;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -45,7 +44,6 @@ 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;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
|
||||
import org.eclipse.che.multiuser.machine.authentication.shared.Constants;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
|
@ -66,6 +64,7 @@ public class KeycloakEnvironmentInitalizationFilterTest {
|
|||
@Mock private HttpServletRequest request;
|
||||
@Mock private HttpServletResponse response;
|
||||
@Mock private HttpSession session;
|
||||
@Mock private JwtParser jwtParser;
|
||||
|
||||
private KeycloakEnvironmentInitalizationFilter filter;
|
||||
|
||||
|
|
@ -79,27 +78,17 @@ public class KeycloakEnvironmentInitalizationFilterTest {
|
|||
filter =
|
||||
new KeycloakEnvironmentInitalizationFilter(
|
||||
userManager, tokenExtractor, permissionChecker, keycloakSettings);
|
||||
filter.signatureKeyManager = keyManager;
|
||||
Field parser = filter.getClass().getSuperclass().getDeclaredField("jwtParser");
|
||||
parser.setAccessible(true);
|
||||
parser.set(filter, jwtParser);
|
||||
final KeyPair kp = new KeyPair(mock(PublicKey.class), mock(PrivateKey.class));
|
||||
when(keyManager.getKeyPair()).thenReturn(kp);
|
||||
when(keyManager.getKeyPair(anyString())).thenReturn(kp);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSkipRequestsWithMachineTokens() throws Exception {
|
||||
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(1024);
|
||||
final KeyPair keyPair = kpg.generateKeyPair();
|
||||
when(keyManager.getKeyPair()).thenReturn(keyPair);
|
||||
final Map<String, Object> header = new HashMap<>();
|
||||
header.put("kind", Constants.MACHINE_TOKEN_KIND);
|
||||
final String token =
|
||||
Jwts.builder()
|
||||
.setPayload("payload")
|
||||
.setHeader(header)
|
||||
.signWith(RS512, keyPair.getPrivate())
|
||||
.compact();
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn(token);
|
||||
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("not_null_token");
|
||||
when(jwtParser.parse(anyString())).thenThrow(MachineTokenJwtException.class);
|
||||
// when
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
|
|
@ -113,13 +102,13 @@ public class KeycloakEnvironmentInitalizationFilterTest {
|
|||
|
||||
Subject existingSubject = new SubjectImpl("name", "id1", "token", false);
|
||||
UserImpl user = new UserImpl("id2", "test2@test.com", "username2");
|
||||
Subject expectedSubject = new SubjectImpl(user.getName(), user.getId(), "token2", false);
|
||||
|
||||
ArgumentCaptor<AuthorizedSubject> captor = ArgumentCaptor.forClass(AuthorizedSubject.class);
|
||||
|
||||
DefaultJwt<Claims> claims = createJwt();
|
||||
Subject expectedSubject = new SubjectImpl(user.getName(), user.getId(), "token2", false);
|
||||
// given
|
||||
when(tokenExtractor.getToken(any(HttpServletRequest.class))).thenReturn("token2");
|
||||
when(request.getAttribute("token")).thenReturn(createJwt());
|
||||
when(request.getAttribute("token")).thenReturn(claims);
|
||||
when(session.getAttribute(eq("che_subject"))).thenReturn(existingSubject);
|
||||
when(userManager.getOrCreateUser(anyString(), anyString(), anyString())).thenReturn(user);
|
||||
EnvironmentContext context = spy(EnvironmentContext.getCurrent());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.keycloak.server;
|
||||
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
import com.auth0.jwk.Jwk;
|
||||
import com.auth0.jwk.JwkProvider;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.impl.DefaultClaims;
|
||||
import io.jsonwebtoken.impl.DefaultJwsHeader;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
import org.testng.annotations.Listeners;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
@Listeners(MockitoTestNGListener.class)
|
||||
public class KeycloakSigningKeyResolverTest {
|
||||
|
||||
@Mock private JwkProvider jwkProvider;
|
||||
|
||||
@InjectMocks private KeycloakSigningKeyResolver signingKeyResolver;
|
||||
|
||||
@Test(expectedExceptions = MachineTokenJwtException.class)
|
||||
public void shouldThrowMachineTokenExceptionOnMachineTokensWithPlainText() {
|
||||
final Map<String, Object> param = new HashMap<>();
|
||||
param.put("kind", MACHINE_TOKEN_KIND);
|
||||
DefaultJwsHeader header = new DefaultJwsHeader(param);
|
||||
|
||||
signingKeyResolver.resolveSigningKey(header, "plaintext");
|
||||
verifyNoMoreInteractions(jwkProvider);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = MachineTokenJwtException.class)
|
||||
public void shouldThrowMachineTokenExceptionOnMachineTokensWithClaims() {
|
||||
final Map<String, Object> param = new HashMap<>();
|
||||
param.put("kind", MACHINE_TOKEN_KIND);
|
||||
DefaultJwsHeader header = new DefaultJwsHeader(param);
|
||||
|
||||
signingKeyResolver.resolveSigningKey(header, new DefaultClaims());
|
||||
verifyNoMoreInteractions(jwkProvider);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = JwtException.class)
|
||||
public void shouldThrowJwtExceptionifNoKeyIdHeader() {
|
||||
|
||||
signingKeyResolver.resolveSigningKey(new DefaultJwsHeader(), "plaintext");
|
||||
verifyNoMoreInteractions(jwkProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnPublicKey() throws Exception {
|
||||
final String kid = "123";
|
||||
final Jwk jwk = mock(Jwk.class);
|
||||
final Map<String, Object> param = new HashMap<>();
|
||||
param.put("kid", kid);
|
||||
final KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(1024);
|
||||
final KeyPair keyPair = kpg.generateKeyPair();
|
||||
|
||||
when(jwk.getPublicKey()).thenReturn(keyPair.getPublic());
|
||||
when(jwkProvider.get(eq(kid))).thenReturn(jwk);
|
||||
|
||||
Key actual = signingKeyResolver.resolveSigningKey(new DefaultJwsHeader(param), "plaintext");
|
||||
assertEquals(actual, keyPair.getPublic());
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,10 @@
|
|||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-account</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.che.core</groupId>
|
||||
<artifactId>che-core-api-core</artifactId>
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ public class MachineAuthModule extends AbstractModule {
|
|||
|
||||
bind(SignatureKeyManager.class);
|
||||
bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class);
|
||||
bind(JpaSignatureKeyDao.RemoveKeyPairsBeforeWorkspaceRemovedEventSubscriber.class)
|
||||
.asEagerSingleton();
|
||||
final Multibinder<EnvVarProvider> envVarProviders =
|
||||
Multibinder.newSetBinder(binder(), EnvVarProvider.class);
|
||||
envVarProviders.addBinding().to(SignaturePublicKeyEnvProvider.class);
|
||||
|
|
|
|||
|
|
@ -14,16 +14,12 @@ package org.eclipse.che.multiuser.machine.authentication.server;
|
|||
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.MACHINE_TOKEN_KIND;
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.USER_ID_CLAIM;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jws;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.JwtParser;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import java.io.IOException;
|
||||
import java.security.Principal;
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -46,7 +42,6 @@ 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;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
|
||||
|
||||
/**
|
||||
* Handles requests that comes from machines with specific machine token.
|
||||
|
|
@ -59,46 +54,36 @@ public class MachineLoginFilter implements Filter {
|
|||
|
||||
private final RequestTokenExtractor tokenExtractor;
|
||||
private final UserManager userManager;
|
||||
private final SignatureKeyManager keyManager;
|
||||
private final PermissionChecker permissionChecker;
|
||||
private final JwtParser jwtParser;
|
||||
|
||||
@Inject
|
||||
public MachineLoginFilter(
|
||||
RequestTokenExtractor tokenExtractor,
|
||||
UserManager userManager,
|
||||
SignatureKeyManager keyManager,
|
||||
MachineSigningKeyResolver machineKeyResolver,
|
||||
PermissionChecker permissionChecker) {
|
||||
this.tokenExtractor = tokenExtractor;
|
||||
this.userManager = userManager;
|
||||
this.keyManager = keyManager;
|
||||
this.permissionChecker = permissionChecker;
|
||||
this.jwtParser = Jwts.parser().setSigningKeyResolver(machineKeyResolver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {}
|
||||
public void init(FilterConfig filterConfig) {}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
final HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
final String token = tokenExtractor.getToken(httpRequest);
|
||||
|
||||
if (isNullOrEmpty(token)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
// check token signature and verify is this token machine or not
|
||||
try {
|
||||
final Jws<Claims> jwt =
|
||||
Jwts.parser().setSigningKey(keyManager.getKeyPair().getPublic()).parseClaimsJws(token);
|
||||
final Claims claims = jwt.getBody();
|
||||
|
||||
if (!isMachineToken(jwt)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
final Claims claims = jwtParser.parseClaimsJws(token).getBody();
|
||||
try {
|
||||
final String userId = claims.get(USER_ID_CLAIM, String.class);
|
||||
// check if user with such id exists
|
||||
|
|
@ -113,28 +98,20 @@ public class MachineLoginFilter implements Filter {
|
|||
response,
|
||||
SC_UNAUTHORIZED,
|
||||
"Authentication with machine token failed because user for this token no longer exist.");
|
||||
} catch (ServerException ex) {
|
||||
sendErr(
|
||||
response,
|
||||
SC_UNAUTHORIZED,
|
||||
format("Authentication with machine token failed cause: %s", ex.getMessage()));
|
||||
} finally {
|
||||
EnvironmentContext.reset();
|
||||
}
|
||||
} catch (UnsupportedJwtException
|
||||
| MalformedJwtException
|
||||
| SignatureException
|
||||
| ExpiredJwtException ex) {
|
||||
// signature check failed
|
||||
} catch (NotMachineTokenJwtException ex) {
|
||||
// not a machine token, bypass
|
||||
chain.doFilter(request, response);
|
||||
} catch (ServerException | JwtException e) {
|
||||
sendErr(
|
||||
response,
|
||||
SC_UNAUTHORIZED,
|
||||
format("Authentication with machine token failed cause: %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks whether given token from a machine. */
|
||||
private boolean isMachineToken(Jws<Claims> jwt) {
|
||||
return MACHINE_TOKEN_KIND.equals(jwt.getHeader().get("kind"));
|
||||
}
|
||||
|
||||
/** Sets given error code with err message into give response. */
|
||||
private static void sendErr(ServletResponse res, int errCode, String msg) throws IOException {
|
||||
final HttpServletResponse response = (HttpServletResponse) res;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public class MachineSessionInvalidator implements EventSubscriber<WorkspaceStatu
|
|||
@Override
|
||||
public void onEvent(WorkspaceStatusEvent event) {
|
||||
if (WorkspaceStatus.STOPPED.equals(event.getStatus())) {
|
||||
tokenRegistry.removeTokens(event.getWorkspaceId()).values();
|
||||
tokenRegistry.removeTokens(event.getWorkspaceId());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.eclipse.che.multiuser.machine.authentication.shared.Constants.MACHINE_TOKEN_KIND;
|
||||
import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.WORKSPACE_ID_CLAIM;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||
import java.security.Key;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyManager;
|
||||
|
||||
/** Resolves signing key pair based on workspace Id claim of token. */
|
||||
@Singleton
|
||||
public class MachineSigningKeyResolver extends SigningKeyResolverAdapter {
|
||||
|
||||
private final SignatureKeyManager keyManager;
|
||||
|
||||
@Inject
|
||||
public MachineSigningKeyResolver(SignatureKeyManager keyManager) {
|
||||
this.keyManager = keyManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Key resolveSigningKey(JwsHeader header, Claims claims) {
|
||||
if (!MACHINE_TOKEN_KIND.equals(header.get("kind"))) {
|
||||
throw new NotMachineTokenJwtException();
|
||||
}
|
||||
String wsId = claims.get(WORKSPACE_ID_CLAIM, String.class);
|
||||
if (wsId == null) {
|
||||
throw new JwtException(
|
||||
"Unable to fetch signature key pair: no workspace id present in token");
|
||||
}
|
||||
try {
|
||||
return keyManager.getKeyPair(wsId).getPublic();
|
||||
} catch (ServerException e) {
|
||||
throw new JwtException("Unable to fetch signature key pair:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ public class MachineTokenRegistry {
|
|||
/** Creates new token with given data. */
|
||||
private String createToken(String userId, String workspaceId)
|
||||
throws NotFoundException, ServerException {
|
||||
final PrivateKey privateKey = signatureKeyManager.getKeyPair().getPrivate();
|
||||
final PrivateKey privateKey = signatureKeyManager.getKeyPair(workspaceId).getPrivate();
|
||||
final User user = userManager.getById(userId);
|
||||
final Map<String, Object> header = new HashMap<>(2);
|
||||
header.put("kind", MACHINE_TOKEN_KIND);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 io.jsonwebtoken.JwtException;
|
||||
|
||||
public class NotMachineTokenJwtException extends JwtException {
|
||||
|
||||
public NotMachineTokenJwtException() {
|
||||
super("This is not a machine token");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,10 +11,13 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.machine.authentication.server.signature;
|
||||
|
||||
import static org.eclipse.che.commons.lang.NameGenerator.generate;
|
||||
import static org.eclipse.che.api.core.model.workspace.WorkspaceStatus.STOPPED;
|
||||
|
||||
import com.google.common.annotations.Beta;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
|
|
@ -25,13 +28,18 @@ import java.security.spec.EncodedKeySpec;
|
|||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.notification.EventService;
|
||||
import org.eclipse.che.api.core.notification.EventSubscriber;
|
||||
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
|
||||
import org.eclipse.che.commons.annotation.Nullable;
|
||||
import org.eclipse.che.core.db.DBInitializer;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl;
|
||||
|
|
@ -57,56 +65,95 @@ public class SignatureKeyManager {
|
|||
|
||||
private final String algorithm;
|
||||
private final SignatureKeyDao signatureKeyDao;
|
||||
private final EventService eventService;
|
||||
private final EventSubscriber<?> workspaceEventsSubscriber;
|
||||
|
||||
@Inject
|
||||
@SuppressWarnings("unused")
|
||||
private DBInitializer dbInitializer;
|
||||
|
||||
private KeyPair cachedPair;
|
||||
private LoadingCache<String, KeyPair> cachedPair;
|
||||
|
||||
@Inject
|
||||
public SignatureKeyManager(
|
||||
@Named("che.auth.signature_key_size") int keySize,
|
||||
@Named("che.auth.signature_key_algorithm") String algorithm,
|
||||
EventService eventService,
|
||||
SignatureKeyDao signatureKeyDao) {
|
||||
this.keySize = keySize;
|
||||
this.algorithm = algorithm;
|
||||
this.eventService = eventService;
|
||||
this.signatureKeyDao = signatureKeyDao;
|
||||
|
||||
this.cachedPair =
|
||||
CacheBuilder.newBuilder()
|
||||
.maximumSize(100)
|
||||
.expireAfterAccess(2, TimeUnit.HOURS)
|
||||
.build(
|
||||
new CacheLoader<String, KeyPair>() {
|
||||
@Override
|
||||
public KeyPair load(String key) throws Exception {
|
||||
return loadKeyPair(key);
|
||||
}
|
||||
});
|
||||
|
||||
this.workspaceEventsSubscriber =
|
||||
new EventSubscriber<WorkspaceStatusEvent>() {
|
||||
@Override
|
||||
public void onEvent(WorkspaceStatusEvent event) {
|
||||
if (event.getStatus() == STOPPED) {
|
||||
removeKeyPair(event.getWorkspaceId());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns cached instance of {@link KeyPair} or null when failed to load key pair. */
|
||||
@Nullable
|
||||
public KeyPair getKeyPair() {
|
||||
if (cachedPair == null) {
|
||||
loadKeyPair();
|
||||
public KeyPair getKeyPair(String workspaceId) throws ServerException {
|
||||
try {
|
||||
return cachedPair.get(workspaceId);
|
||||
} catch (ExecutionException e) {
|
||||
throw new ServerException(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes key pair from cache and DB. */
|
||||
public void removeKeyPair(String workspaceId) {
|
||||
try {
|
||||
cachedPair.invalidate(workspaceId);
|
||||
signatureKeyDao.remove(workspaceId);
|
||||
} catch (ServerException e) {
|
||||
LOG.error(
|
||||
"Unable to cleanup machine token signature keypairs for ws {}. Cause: {}",
|
||||
workspaceId,
|
||||
e.getMessage());
|
||||
}
|
||||
return cachedPair;
|
||||
}
|
||||
|
||||
/** Loads signature key pair if no existing keys found then stores a newly generated key pair. */
|
||||
@PostConstruct
|
||||
@VisibleForTesting
|
||||
void loadKeyPair() {
|
||||
KeyPair loadKeyPair(String workspaceId) throws ServerException, ConflictException {
|
||||
try {
|
||||
final Iterator<SignatureKeyPairImpl> it = signatureKeyDao.getAll(1, 0).getItems().iterator();
|
||||
if (it.hasNext()) {
|
||||
cachedPair = toJavaKeyPair(it.next());
|
||||
return;
|
||||
return toJavaKeyPair(signatureKeyDao.get(workspaceId));
|
||||
} catch (NotFoundException nfe) {
|
||||
try {
|
||||
return toJavaKeyPair(signatureKeyDao.create(generateKeyPair(workspaceId)));
|
||||
} catch (ConflictException | ServerException ex) {
|
||||
LOG.error(
|
||||
"Failed to store signature keys for ws {}. Cause: {}", workspaceId, ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
} catch (ServerException ex) {
|
||||
LOG.error("Failed to load signature keys. Cause: {}", ex.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
cachedPair = toJavaKeyPair(signatureKeyDao.create(generateKeyPair()));
|
||||
} catch (ConflictException | ServerException ex) {
|
||||
LOG.error("Failed to store signature keys. Cause: {}", ex.getMessage());
|
||||
LOG.error(
|
||||
"Failed to load signature keys for ws {}. Cause: {}", workspaceId, ex.getMessage());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SignatureKeyPairImpl generateKeyPair() throws ServerException {
|
||||
SignatureKeyPairImpl generateKeyPair(String workspaceId) throws ServerException {
|
||||
final KeyPairGenerator kpg;
|
||||
try {
|
||||
kpg = KeyPairGenerator.getInstance(algorithm);
|
||||
|
|
@ -116,8 +163,11 @@ public class SignatureKeyManager {
|
|||
kpg.initialize(keySize);
|
||||
final KeyPair pair = kpg.generateKeyPair();
|
||||
final SignatureKeyPairImpl kp =
|
||||
new SignatureKeyPairImpl(generate("signatureKey", 16), pair.getPublic(), pair.getPrivate());
|
||||
LOG.info("Generated signature key pair with id {} and algorithm {}.", kp.getId(), algorithm);
|
||||
new SignatureKeyPairImpl(workspaceId, pair.getPublic(), pair.getPrivate());
|
||||
LOG.debug(
|
||||
"Generated signature key pair with ws id {} and algorithm {}.",
|
||||
kp.getWorkspaceId(),
|
||||
algorithm);
|
||||
return kp;
|
||||
}
|
||||
|
||||
|
|
@ -149,4 +199,10 @@ public class SignatureKeyManager {
|
|||
String.format("Unsupported key spec '%s' for signature keys", key.getFormat()));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@PostConstruct
|
||||
void subscribe() {
|
||||
eventService.subscribe(workspaceEventsSubscriber);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import com.google.common.annotations.Beta;
|
|||
@Beta
|
||||
public interface SignatureKeyPair {
|
||||
|
||||
/** Returns unique identifier for this sign key pair. */
|
||||
String getId();
|
||||
/** Returns workspace identifier for this sign key pair. */
|
||||
String getWorkspaceId();
|
||||
|
||||
/** Returns public part for this sign key pair. */
|
||||
SignatureKey getPublicKey();
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ import static org.eclipse.che.multiuser.machine.authentication.shared.Constants.
|
|||
|
||||
import java.util.Base64;
|
||||
import javax.inject.Inject;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity;
|
||||
import org.eclipse.che.api.workspace.server.spi.InfrastructureException;
|
||||
import org.eclipse.che.api.workspace.server.spi.provision.env.EnvVarProvider;
|
||||
import org.eclipse.che.commons.lang.Pair;
|
||||
|
||||
|
|
@ -34,9 +36,21 @@ public class SignaturePublicKeyEnvProvider implements EnvVarProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, String> get(RuntimeIdentity runtimeIdentity) {
|
||||
return Pair.of(
|
||||
SIGNATURE_PUBLIC_KEY_ENV,
|
||||
new String(Base64.getEncoder().encode(keyManager.getKeyPair().getPublic().getEncoded())));
|
||||
public Pair<String, String> get(RuntimeIdentity runtimeIdentity) throws InfrastructureException {
|
||||
try {
|
||||
return Pair.of(
|
||||
SIGNATURE_PUBLIC_KEY_ENV,
|
||||
new String(
|
||||
Base64.getEncoder()
|
||||
.encode(
|
||||
keyManager
|
||||
.getKeyPair(runtimeIdentity.getWorkspaceId())
|
||||
.getPublic()
|
||||
.getEncoded())));
|
||||
} catch (ServerException e) {
|
||||
throw new InfrastructureException(
|
||||
"Signature key pair for machine authentication cannot be retrieved. Reason: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,20 +11,23 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.machine.authentication.server.signature.jpa;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.google.inject.persist.Transactional;
|
||||
import java.util.List;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.Page;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.notification.EventService;
|
||||
import org.eclipse.che.api.workspace.server.event.BeforeWorkspaceRemovedEvent;
|
||||
import org.eclipse.che.core.db.cascade.CascadeEventSubscriber;
|
||||
import org.eclipse.che.core.db.jpa.DuplicateKeyException;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao;
|
||||
|
|
@ -52,7 +55,7 @@ public class JpaSignatureKeyDao implements SignatureKeyDao {
|
|||
doCreate(keyPair);
|
||||
} catch (DuplicateKeyException dkEx) {
|
||||
throw new ConflictException(
|
||||
format("Signature key pair with id '%s' already exists", keyPair.getId()));
|
||||
format("Signature key pair for workspace '%s' already exists", keyPair.getWorkspaceId()));
|
||||
} catch (RuntimeException ex) {
|
||||
throw new ServerException(ex.getMessage(), ex);
|
||||
}
|
||||
|
|
@ -67,20 +70,20 @@ public class JpaSignatureKeyDao implements SignatureKeyDao {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void remove(String id) throws ServerException {
|
||||
requireNonNull(id, "Required non-null key pair");
|
||||
public void remove(String workspaceId) throws ServerException {
|
||||
requireNonNull(workspaceId, "Required non-null workspace Id");
|
||||
try {
|
||||
doRemove(id);
|
||||
doRemove(workspaceId);
|
||||
} catch (RuntimeException ex) {
|
||||
throw new ServerException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
protected void doRemove(String id) {
|
||||
final SignatureKeyPairImpl keyPair = managerProvider.get().find(SignatureKeyPairImpl.class, id);
|
||||
protected void doRemove(String workspaceId) {
|
||||
final EntityManager manager = managerProvider.get();
|
||||
final SignatureKeyPairImpl keyPair = manager.find(SignatureKeyPairImpl.class, workspaceId);
|
||||
if (keyPair != null) {
|
||||
final EntityManager manager = managerProvider.get();
|
||||
manager.remove(keyPair);
|
||||
manager.flush();
|
||||
}
|
||||
|
|
@ -88,27 +91,41 @@ public class JpaSignatureKeyDao implements SignatureKeyDao {
|
|||
|
||||
@Override
|
||||
@Transactional
|
||||
public Page<SignatureKeyPairImpl> getAll(int maxItems, long skipCount) throws ServerException {
|
||||
checkArgument(maxItems >= 0, "The number of items to return can't be negative.");
|
||||
checkArgument(
|
||||
skipCount >= 0,
|
||||
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
|
||||
public SignatureKeyPairImpl get(String workspaceId) throws NotFoundException, ServerException {
|
||||
final EntityManager manager = managerProvider.get();
|
||||
try {
|
||||
final EntityManager manager = managerProvider.get();
|
||||
final List<SignatureKeyPairImpl> list =
|
||||
return new SignatureKeyPairImpl(
|
||||
manager
|
||||
.createNamedQuery("SignKeyPair.getAll", SignatureKeyPairImpl.class)
|
||||
.setMaxResults(maxItems)
|
||||
.setFirstResult((int) skipCount)
|
||||
.getResultList()
|
||||
.stream()
|
||||
.map(SignatureKeyPairImpl::new)
|
||||
.collect(toList());
|
||||
final long count =
|
||||
manager.createNamedQuery("SignKeyPair.getAllCount", Long.class).getSingleResult();
|
||||
return new Page<>(list, skipCount, maxItems, count);
|
||||
.setParameter("workspaceId", workspaceId)
|
||||
.getSingleResult());
|
||||
} catch (NoResultException x) {
|
||||
throw new NotFoundException(
|
||||
format("Signature key pair for workspace '%s' doesn't exist", workspaceId));
|
||||
} catch (RuntimeException ex) {
|
||||
throw new ServerException(ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Singleton
|
||||
public static class RemoveKeyPairsBeforeWorkspaceRemovedEventSubscriber
|
||||
extends CascadeEventSubscriber<BeforeWorkspaceRemovedEvent> {
|
||||
@Inject private EventService eventService;
|
||||
@Inject private SignatureKeyDao signatureKeyDao;
|
||||
|
||||
@PostConstruct
|
||||
public void subscribe() {
|
||||
eventService.subscribe(this, BeforeWorkspaceRemovedEvent.class);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void unsubscribe() {
|
||||
eventService.unsubscribe(this, BeforeWorkspaceRemovedEvent.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCascadeEvent(BeforeWorkspaceRemovedEvent event) throws Exception {
|
||||
signatureKeyDao.remove(event.getWorkspace().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,20 +23,26 @@ import javax.persistence.NamedQueries;
|
|||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Table;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.SignatureKeyPair;
|
||||
|
||||
/** @author Anton Korneta */
|
||||
@Entity(name = "SignKeyPair")
|
||||
@Table(name = "che_sign_key_pair")
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "SignKeyPair.getAll", query = "SELECT kp FROM SignKeyPair kp"),
|
||||
@NamedQuery(name = "SignKeyPair.getAllCount", query = "SELECT COUNT(kp) FROM SignKeyPair kp")
|
||||
@NamedQuery(
|
||||
name = "SignKeyPair.getAll",
|
||||
query = "SELECT kp FROM SignKeyPair kp WHERE kp.workspaceId = :workspaceId"),
|
||||
})
|
||||
public class SignatureKeyPairImpl implements SignatureKeyPair {
|
||||
|
||||
@Id
|
||||
@Column(name = "id")
|
||||
private String id;
|
||||
@Column(name = "workspace_id")
|
||||
private String workspaceId;
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "workspace_id", insertable = false, updatable = false)
|
||||
private WorkspaceImpl workspace;
|
||||
|
||||
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@JoinColumn(name = "public_key")
|
||||
|
|
@ -49,26 +55,27 @@ public class SignatureKeyPairImpl implements SignatureKeyPair {
|
|||
public SignatureKeyPairImpl() {}
|
||||
|
||||
public SignatureKeyPairImpl(SignatureKeyPairImpl keyPair) {
|
||||
this(keyPair.getId(), keyPair.getPublicKey(), keyPair.getPrivateKey());
|
||||
this(keyPair.getWorkspaceId(), keyPair.getPublicKey(), keyPair.getPrivateKey());
|
||||
}
|
||||
|
||||
public SignatureKeyPairImpl(String id, PublicKey publicKey, PrivateKey privateKey) {
|
||||
this(id, new SignatureKeyImpl(publicKey), new SignatureKeyImpl(privateKey));
|
||||
public SignatureKeyPairImpl(String workspaceId, PublicKey publicKey, PrivateKey privateKey) {
|
||||
this(workspaceId, new SignatureKeyImpl(publicKey), new SignatureKeyImpl(privateKey));
|
||||
}
|
||||
|
||||
public SignatureKeyPairImpl(String id, SignatureKeyImpl publicKey, SignatureKeyImpl privateKey) {
|
||||
this.id = id;
|
||||
public SignatureKeyPairImpl(
|
||||
String workspaceId, SignatureKeyImpl publicKey, SignatureKeyImpl privateKey) {
|
||||
this.workspaceId = workspaceId;
|
||||
this.publicKey = publicKey;
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
public String getWorkspaceId() {
|
||||
return workspaceId;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
public void setWorkspaceId(String workspaceId) {
|
||||
this.workspaceId = workspaceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -98,7 +105,7 @@ public class SignatureKeyPairImpl implements SignatureKeyPair {
|
|||
return false;
|
||||
}
|
||||
final SignatureKeyPairImpl that = (SignatureKeyPairImpl) obj;
|
||||
return Objects.equals(id, that.id)
|
||||
return Objects.equals(workspaceId, that.workspaceId)
|
||||
&& Objects.equals(publicKey, that.publicKey)
|
||||
&& Objects.equals(privateKey, that.privateKey);
|
||||
}
|
||||
|
|
@ -106,7 +113,7 @@ public class SignatureKeyPairImpl implements SignatureKeyPair {
|
|||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 7;
|
||||
hash = 31 * hash + Objects.hashCode(id);
|
||||
hash = 31 * hash + Objects.hashCode(workspaceId);
|
||||
hash = 31 * hash + Objects.hashCode(publicKey);
|
||||
hash = 31 * hash + Objects.hashCode(privateKey);
|
||||
return hash;
|
||||
|
|
@ -115,8 +122,8 @@ public class SignatureKeyPairImpl implements SignatureKeyPair {
|
|||
@Override
|
||||
public String toString() {
|
||||
return "SignatureKeyPairImpl{"
|
||||
+ "id='"
|
||||
+ id
|
||||
+ "workspaceId='"
|
||||
+ workspaceId
|
||||
+ '\''
|
||||
+ ", publicKey="
|
||||
+ publicKey
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ package org.eclipse.che.multiuser.machine.authentication.server.signature.spi;
|
|||
|
||||
import com.google.common.annotations.Beta;
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.Page;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl;
|
||||
|
||||
|
|
@ -36,21 +36,19 @@ public interface SignatureKeyDao {
|
|||
throws ConflictException, ServerException;
|
||||
|
||||
/**
|
||||
* Removes signature key pair with given id.
|
||||
* Removes signature key pair with given workspace id.
|
||||
*
|
||||
* @param id signature key identifier
|
||||
* @param workspaceId workspace identifier to remove keypair from
|
||||
* @throws ServerException when any errors occur while removing signature key pair
|
||||
*/
|
||||
void remove(String id) throws ServerException;
|
||||
void remove(String workspaceId) throws ServerException;
|
||||
|
||||
/**
|
||||
* Returns all the signature key pairs.
|
||||
* Returns signature key pair for given workspace id.
|
||||
*
|
||||
* @param skipCount the number of signature key pairs to skip
|
||||
* @param maxItems the maximum number of signature key pairs to return
|
||||
* @return list of signature key pairs or an empty list when no keys were found
|
||||
* @throws ServerException when any errors occur while fetching the key pairs
|
||||
* @throws IllegalArgumentException when {@code maxItems} or {@code skipCount} is negative
|
||||
* @param workspaceId identifier of workspace which key pair belongs to
|
||||
* @return signature key pair for the given workspace
|
||||
* @throws NotFoundException when any errors occur while fetching the key pairs
|
||||
*/
|
||||
Page<SignatureKeyPairImpl> getAll(int maxItems, long skipCount) throws ServerException;
|
||||
SignatureKeyPairImpl get(String workspaceId) throws NotFoundException, ServerException;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,10 +100,13 @@ public class MachineLoginFilterTest {
|
|||
.compact();
|
||||
machineLoginFilter =
|
||||
new MachineLoginFilter(
|
||||
tokenExtractorMock, userManagerMock, keyManagerMock, permissionCheckerMock);
|
||||
tokenExtractorMock,
|
||||
userManagerMock,
|
||||
new MachineSigningKeyResolver(keyManagerMock),
|
||||
permissionCheckerMock);
|
||||
|
||||
when(tokenExtractorMock.getToken(any(HttpServletRequest.class))).thenReturn(token);
|
||||
when(keyManagerMock.getKeyPair()).thenReturn(keyPair);
|
||||
when(keyManagerMock.getKeyPair(eq(WORKSPACE_ID))).thenReturn(keyPair);
|
||||
|
||||
when(userMock.getName()).thenReturn(SUBJECT.getUserName());
|
||||
when(userManagerMock.getById(SUBJECT.getUserId())).thenReturn(userMock);
|
||||
|
|
@ -113,25 +116,27 @@ public class MachineLoginFilterTest {
|
|||
public void testProcessRequestWithValidToken() throws Exception {
|
||||
machineLoginFilter.doFilter(getRequestMock(), responseMock, chainMock);
|
||||
|
||||
verify(keyManagerMock).getKeyPair();
|
||||
verify(keyManagerMock).getKeyPair(eq(WORKSPACE_ID));
|
||||
verify(userManagerMock).getById(anyString());
|
||||
verifyZeroInteractions(responseMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProceedRequestWhenSignatureCheckIsFailed() throws Exception {
|
||||
final String tokenWithInvalidSignature = "keycloak_token";
|
||||
public void testNotProceedRequestWhenSignatureCheckIsFailed() throws Exception {
|
||||
final HttpServletRequest requestMock = getRequestMock();
|
||||
when(tokenExtractorMock.getToken(any(HttpServletRequest.class)))
|
||||
.thenReturn(tokenWithInvalidSignature);
|
||||
final KeyPairGenerator kpg = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM);
|
||||
kpg.initialize(KEY_SIZE);
|
||||
final KeyPair pair = kpg.generateKeyPair();
|
||||
when(keyManagerMock.getKeyPair(eq(WORKSPACE_ID))).thenReturn(pair);
|
||||
|
||||
machineLoginFilter.doFilter(requestMock, responseMock, chainMock);
|
||||
|
||||
verify(tokenExtractorMock).getToken(any(HttpServletRequest.class));
|
||||
verify(keyManagerMock).getKeyPair();
|
||||
verify(chainMock).doFilter(requestMock, responseMock);
|
||||
verifyZeroInteractions(userManagerMock);
|
||||
verifyZeroInteractions(responseMock);
|
||||
verify(responseMock)
|
||||
.sendError(
|
||||
401,
|
||||
"Authentication with machine token failed cause: JWT signature does not match locally computed signature."
|
||||
+ " JWT validity cannot be asserted and should not be trusted.");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -154,7 +159,7 @@ public class MachineLoginFilterTest {
|
|||
|
||||
machineLoginFilter.doFilter(getRequestMock(), responseMock, chainMock);
|
||||
|
||||
verify(keyManagerMock).getKeyPair();
|
||||
verify(keyManagerMock).getKeyPair(eq(WORKSPACE_ID));
|
||||
verify(userManagerMock).getById(anyString());
|
||||
verify(responseMock)
|
||||
.sendError(
|
||||
|
|
@ -168,7 +173,7 @@ public class MachineLoginFilterTest {
|
|||
|
||||
machineLoginFilter.doFilter(getRequestMock(), responseMock, chainMock);
|
||||
|
||||
verify(keyManagerMock).getKeyPair();
|
||||
verify(keyManagerMock).getKeyPair(eq(WORKSPACE_ID));
|
||||
verify(userManagerMock).getById(anyString());
|
||||
verify(responseMock)
|
||||
.sendError(
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class MachineTokenRegistryTest {
|
|||
keyPair = kpg.generateKeyPair();
|
||||
|
||||
mockUser(USER_ID, USER_NAME);
|
||||
when(signatureKeyManager.getKeyPair()).thenReturn(keyPair);
|
||||
when(signatureKeyManager.getKeyPair(anyString())).thenReturn(keyPair);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -89,7 +89,7 @@ public class MachineTokenRegistryTest {
|
|||
assertEquals(subject.getUserName(), USER_NAME);
|
||||
assertEquals(claims.get(WORKSPACE_ID_CLAIM, String.class), WORKSPACE_ID);
|
||||
verify(userManager).getById(USER_ID);
|
||||
verify(signatureKeyManager).getKeyPair();
|
||||
verify(signatureKeyManager).getKeyPair(anyString());
|
||||
assertNotNull(generatedToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,26 +11,32 @@
|
|||
*/
|
||||
package org.eclipse.che.multiuser.machine.authentication.server.signature;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.singleton;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
import static org.testng.Assert.assertNull;
|
||||
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import org.eclipse.che.api.core.Page;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.core.ServerException;
|
||||
import org.eclipse.che.api.core.model.workspace.WorkspaceStatus;
|
||||
import org.eclipse.che.api.core.notification.EventService;
|
||||
import org.eclipse.che.api.core.notification.EventSubscriber;
|
||||
import org.eclipse.che.api.workspace.shared.dto.event.WorkspaceStatusEvent;
|
||||
import org.eclipse.che.dto.server.DtoFactory;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyImpl;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.model.impl.SignatureKeyPairImpl;
|
||||
import org.eclipse.che.multiuser.machine.authentication.server.signature.spi.SignatureKeyDao;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.mockito.testng.MockitoTestNGListener;
|
||||
|
|
@ -50,6 +56,9 @@ public class SignatureKeyManagerTest {
|
|||
private static final String ALGORITHM = "RSA";
|
||||
|
||||
@Mock SignatureKeyDao signatureKeyDao;
|
||||
@Mock EventService eventService;
|
||||
|
||||
@Captor private ArgumentCaptor<EventSubscriber<WorkspaceStatusEvent>> captor;
|
||||
|
||||
private KeyPairGenerator kpg;
|
||||
private SignatureKeyManager signatureKeyManager;
|
||||
|
|
@ -58,18 +67,19 @@ public class SignatureKeyManagerTest {
|
|||
public void createEntities() throws Exception {
|
||||
kpg = KeyPairGenerator.getInstance(ALGORITHM);
|
||||
kpg.initialize(KEY_SIZE);
|
||||
signatureKeyManager = new SignatureKeyManager(KEY_SIZE, ALGORITHM, signatureKeyDao);
|
||||
signatureKeyManager =
|
||||
new SignatureKeyManager(KEY_SIZE, ALGORITHM, eventService, signatureKeyDao);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadSignatureKeys() throws Exception {
|
||||
final SignatureKeyPairImpl kp = newKeyPair("id_" + 1);
|
||||
when(signatureKeyDao.getAll(anyInt(), anyLong()))
|
||||
.thenReturn(new Page<>(singleton(kp), 0, 1, 1));
|
||||
String wsId = "WS_id_1";
|
||||
final SignatureKeyPairImpl kp = newKeyPair(wsId);
|
||||
when(signatureKeyDao.get(anyString())).thenReturn(kp);
|
||||
|
||||
signatureKeyManager.loadKeyPair();
|
||||
signatureKeyManager.loadKeyPair(wsId);
|
||||
|
||||
final KeyPair cachedPair = signatureKeyManager.getKeyPair();
|
||||
final KeyPair cachedPair = signatureKeyManager.getKeyPair(wsId);
|
||||
assertNotNull(cachedPair);
|
||||
assertKeys(cachedPair.getPublic(), kp.getPublicKey());
|
||||
assertKeys(cachedPair.getPrivate(), kp.getPrivateKey());
|
||||
|
|
@ -77,51 +87,67 @@ public class SignatureKeyManagerTest {
|
|||
|
||||
@Test
|
||||
public void testTriesToLoadKeysOnGettingKeyPairAndNoCachedKeyPair() throws Exception {
|
||||
when(signatureKeyDao.getAll(anyInt(), anyLong()))
|
||||
.thenThrow(new ServerException("unexpected end of stack"));
|
||||
String wsId = "WS_id_1";
|
||||
final SignatureKeyPairImpl kp = newKeyPair(wsId);
|
||||
when(signatureKeyDao.create(any(SignatureKeyPairImpl.class))).thenReturn(kp);
|
||||
when(signatureKeyDao.get(anyString())).thenThrow(new NotFoundException("not found"));
|
||||
|
||||
signatureKeyManager.getKeyPair();
|
||||
signatureKeyManager.getKeyPair("ws1");
|
||||
|
||||
verify(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
verify(signatureKeyDao).get(anyString());
|
||||
verify(signatureKeyDao).create(any(SignatureKeyPairImpl.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGeneratesNewKeyPairWhenNoExistingKeyPairFound() throws Exception {
|
||||
doReturn(new Page<>(emptyList(), 0, 1, 0)).when(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
doThrow(NotFoundException.class).when(signatureKeyDao).get(anyString());
|
||||
when(signatureKeyDao.create(any(SignatureKeyPairImpl.class)))
|
||||
.thenAnswer((Answer<SignatureKeyPairImpl>) invoke -> invoke.getArgument(0));
|
||||
|
||||
final KeyPair cachedPair = signatureKeyManager.getKeyPair();
|
||||
final KeyPair cachedPair = signatureKeyManager.getKeyPair("ws1");
|
||||
|
||||
verify(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
verify(signatureKeyDao).get(anyString());
|
||||
verify(signatureKeyDao).create(any(SignatureKeyPairImpl.class));
|
||||
assertNotNull(cachedPair);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnNullKeyPairWhenFailedToLoadAndGenerateKeys() throws Exception {
|
||||
doReturn(new Page<>(emptyList(), 0, 1, 0)).when(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
@Test(expectedExceptions = ServerException.class)
|
||||
public void testThrowsExceptionWhenFailedToLoadAndGenerateKeys() throws Exception {
|
||||
doThrow(NotFoundException.class).when(signatureKeyDao).get(anyString());
|
||||
when(signatureKeyDao.create(any(SignatureKeyPairImpl.class)))
|
||||
.thenThrow(new ServerException("unexpected end of stack"));
|
||||
|
||||
final KeyPair cachedPair = signatureKeyManager.getKeyPair();
|
||||
signatureKeyManager.getKeyPair("ws1");
|
||||
|
||||
verify(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
verify(signatureKeyDao).get(anyString());
|
||||
verify(signatureKeyDao).create(any(SignatureKeyPairImpl.class));
|
||||
assertNull(cachedPair);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnNullKeyPairWhenAlgorithmIsNotSupported() throws Exception {
|
||||
@Test(expectedExceptions = ServerException.class)
|
||||
public void testThrowsExceptionWhenAlgorithmIsNotSupported() throws Exception {
|
||||
final SignatureKeyImpl publicKey = new SignatureKeyImpl(new byte[] {}, "ECDH", "PKCS#15");
|
||||
final SignatureKeyImpl privateKey = new SignatureKeyImpl(new byte[] {}, "ECDH", "PKCS#3");
|
||||
final SignatureKeyPairImpl kp = new SignatureKeyPairImpl("id_" + 1, publicKey, privateKey);
|
||||
doReturn(new Page<>(singleton(kp), 0, 1, 1)).when(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
doReturn(kp).when(signatureKeyDao).get(anyString());
|
||||
|
||||
final KeyPair cachedPair = signatureKeyManager.getKeyPair();
|
||||
signatureKeyManager.getKeyPair("ws1");
|
||||
|
||||
verify(signatureKeyDao).getAll(anyInt(), anyLong());
|
||||
assertNull(cachedPair);
|
||||
verify(signatureKeyDao).get(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRemoveKeyPairOnWorkspaceStop() throws Exception {
|
||||
final String wsId = "ws123";
|
||||
signatureKeyManager.subscribe();
|
||||
verify(eventService).subscribe(captor.capture());
|
||||
final EventSubscriber<WorkspaceStatusEvent> subscriber = captor.getValue();
|
||||
|
||||
subscriber.onEvent(
|
||||
DtoFactory.newDto(WorkspaceStatusEvent.class)
|
||||
.withStatus(WorkspaceStatus.STOPPED)
|
||||
.withWorkspaceId(wsId));
|
||||
|
||||
verify(signatureKeyDao, times(1)).remove(eq(wsId));
|
||||
}
|
||||
|
||||
private SignatureKeyPairImpl newKeyPair(String id) {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,20 @@
|
|||
package org.eclipse.che.multiuser.machine.authentication.server.signature.jpa;
|
||||
|
||||
import com.google.inject.TypeLiteral;
|
||||
import java.util.Collection;
|
||||
import org.eclipse.che.account.spi.AccountImpl;
|
||||
import org.eclipse.che.api.user.server.model.impl.UserImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.CommandImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.MachineConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.SourceStorageImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.VolumeImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.stack.StackImpl;
|
||||
import org.eclipse.che.commons.test.db.H2DBTestServer;
|
||||
import org.eclipse.che.commons.test.db.H2JpaCleaner;
|
||||
import org.eclipse.che.commons.test.db.PersistTestModuleBuilder;
|
||||
|
|
@ -19,6 +33,7 @@ import org.eclipse.che.commons.test.tck.TckModule;
|
|||
import org.eclipse.che.commons.test.tck.TckResourcesCleaner;
|
||||
import org.eclipse.che.commons.test.tck.repository.JpaTckRepository;
|
||||
import org.eclipse.che.commons.test.tck.repository.TckRepository;
|
||||
import org.eclipse.che.commons.test.tck.repository.TckRepositoryException;
|
||||
import org.eclipse.che.core.db.DBInitializer;
|
||||
import org.eclipse.che.core.db.h2.jpa.eclipselink.H2ExceptionHandler;
|
||||
import org.eclipse.che.core.db.schema.SchemaInitializer;
|
||||
|
|
@ -37,7 +52,24 @@ public class SignatureKeyTckModule extends TckModule {
|
|||
new PersistTestModuleBuilder()
|
||||
.setDriver("org.h2.Driver")
|
||||
.runningOn(server)
|
||||
.addEntityClasses(SignatureKeyImpl.class, SignatureKeyPairImpl.class)
|
||||
.addEntityClasses(
|
||||
AccountImpl.class,
|
||||
UserImpl.class,
|
||||
SignatureKeyImpl.class,
|
||||
SignatureKeyPairImpl.class,
|
||||
WorkspaceImpl.class,
|
||||
WorkspaceConfigImpl.class,
|
||||
ProjectConfigImpl.class,
|
||||
EnvironmentImpl.class,
|
||||
MachineConfigImpl.class,
|
||||
SourceStorageImpl.class,
|
||||
ServerConfigImpl.class,
|
||||
StackImpl.class,
|
||||
CommandImpl.class,
|
||||
RecipeImpl.class,
|
||||
VolumeImpl.class)
|
||||
.addEntityClass(
|
||||
"org.eclipse.che.api.workspace.server.model.impl.ProjectConfigImpl$Attribute")
|
||||
.setExceptionHandler(H2ExceptionHandler.class)
|
||||
.build());
|
||||
|
||||
|
|
@ -49,5 +81,23 @@ public class SignatureKeyTckModule extends TckModule {
|
|||
bind(SignatureKeyDao.class).to(JpaSignatureKeyDao.class);
|
||||
bind(new TypeLiteral<TckRepository<SignatureKeyPairImpl>>() {})
|
||||
.toInstance(new JpaTckRepository<>(SignatureKeyPairImpl.class));
|
||||
bind(new TypeLiteral<TckRepository<AccountImpl>>() {})
|
||||
.toInstance(new JpaTckRepository<>(AccountImpl.class));
|
||||
bind(new TypeLiteral<TckRepository<WorkspaceImpl>>() {}).toInstance(new WorkspaceRepository());
|
||||
}
|
||||
|
||||
private static class WorkspaceRepository extends JpaTckRepository<WorkspaceImpl> {
|
||||
public WorkspaceRepository() {
|
||||
super(WorkspaceImpl.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAll(Collection<? extends WorkspaceImpl> entities)
|
||||
throws TckRepositoryException {
|
||||
for (WorkspaceImpl entity : entities) {
|
||||
entity.getConfig().getProjects().forEach(ProjectConfigImpl::prePersistAttributes);
|
||||
}
|
||||
super.createAll(entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,22 @@ package org.eclipse.che.multiuser.machine.authentication.server.signature.spi.tc
|
|||
import static java.util.Arrays.asList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.testng.Assert.assertEquals;
|
||||
import static org.testng.Assert.assertFalse;
|
||||
import static org.testng.Assert.assertTrue;
|
||||
import static org.testng.Assert.assertNotNull;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import javax.inject.Inject;
|
||||
import org.eclipse.che.account.spi.AccountImpl;
|
||||
import org.eclipse.che.api.core.ConflictException;
|
||||
import org.eclipse.che.api.core.Page;
|
||||
import org.eclipse.che.api.core.NotFoundException;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl;
|
||||
import org.eclipse.che.api.workspace.server.model.impl.WorkspaceImpl;
|
||||
import org.eclipse.che.commons.test.tck.TckListener;
|
||||
import org.eclipse.che.commons.test.tck.repository.TckRepository;
|
||||
import org.eclipse.che.commons.test.tck.repository.TckRepositoryException;
|
||||
|
|
@ -47,9 +53,11 @@ public class SignatureKeyDaoTest {
|
|||
public static final String ALGORITHM = "RSA";
|
||||
public static final int KEY_SIZE = 512;
|
||||
|
||||
private static final int COUNT_KEY_PAIRS = 5;
|
||||
private static final int COUNT_KEY_PAIRS = 3;
|
||||
|
||||
@Inject private TckRepository<SignatureKeyPairImpl> signatureKeyRepo;
|
||||
@Inject private TckRepository<AccountImpl> accountRepository;
|
||||
@Inject private TckRepository<WorkspaceImpl> workspaceRepository;
|
||||
@Inject private SignatureKeyDao dao;
|
||||
|
||||
private SignatureKeyPairImpl[] storedKeyPairs;
|
||||
|
|
@ -58,15 +66,39 @@ public class SignatureKeyDaoTest {
|
|||
@AfterMethod
|
||||
public void removeEntities() throws TckRepositoryException {
|
||||
signatureKeyRepo.removeAll();
|
||||
workspaceRepository.removeAll();
|
||||
accountRepository.removeAll();
|
||||
}
|
||||
|
||||
@BeforeMethod
|
||||
public void createEntities() throws Exception {
|
||||
|
||||
AccountImpl account = new AccountImpl("account1", "accountName", "test");
|
||||
accountRepository.createAll(Collections.singletonList(account));
|
||||
workspaceRepository.createAll(
|
||||
Arrays.asList(
|
||||
new WorkspaceImpl(
|
||||
"ws0",
|
||||
account,
|
||||
new WorkspaceConfigImpl("ws-name0", "", "cfg0", null, null, null, null)),
|
||||
new WorkspaceImpl(
|
||||
"ws1",
|
||||
account,
|
||||
new WorkspaceConfigImpl("ws-name1", "", "cfg1", null, null, null, null)),
|
||||
new WorkspaceImpl(
|
||||
"ws2",
|
||||
account,
|
||||
new WorkspaceConfigImpl("ws-name2", "", "cfg2", null, null, null, null)),
|
||||
new WorkspaceImpl(
|
||||
"id_10",
|
||||
account,
|
||||
new WorkspaceConfigImpl("ws-name10", "", "cfg1", null, null, null, null))));
|
||||
|
||||
storedKeyPairs = new SignatureKeyPairImpl[COUNT_KEY_PAIRS];
|
||||
kpg = KeyPairGenerator.getInstance(ALGORITHM);
|
||||
kpg.initialize(KEY_SIZE);
|
||||
for (int i = 0; i < COUNT_KEY_PAIRS; i++) {
|
||||
storedKeyPairs[i] = newKeyPair("id_" + i);
|
||||
storedKeyPairs[i] = newKeyPair("ws" + i);
|
||||
}
|
||||
signatureKeyRepo.createAll(
|
||||
Stream.of(storedKeyPairs).map(SignatureKeyPairImpl::new).collect(toList()));
|
||||
|
|
@ -74,54 +106,48 @@ public class SignatureKeyDaoTest {
|
|||
|
||||
@Test
|
||||
public void testGetsAllKeys() throws Exception {
|
||||
final Page<SignatureKeyPairImpl> foundKeys = dao.getAll(COUNT_KEY_PAIRS, 0);
|
||||
|
||||
assertEquals(new HashSet<>(foundKeys.getItems()), new HashSet<>(asList(storedKeyPairs)));
|
||||
assertEquals(foundKeys.getTotalItemsCount(), COUNT_KEY_PAIRS);
|
||||
assertEquals(foundKeys.getItems().size(), COUNT_KEY_PAIRS);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void testThrowsIllegalArgumentExceptionWhenMaxItemsIsNegative() throws Exception {
|
||||
dao.getAll(-1, 0);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = IllegalArgumentException.class)
|
||||
public void testThrowsIllegalArgumentExceptionWhenSkipCountIsNegative() throws Exception {
|
||||
dao.getAll(1, -1);
|
||||
List<SignatureKeyPairImpl> foundKeys = new ArrayList<>();
|
||||
for (SignatureKeyPairImpl expected : storedKeyPairs) {
|
||||
foundKeys.add(dao.get(expected.getWorkspaceId()));
|
||||
}
|
||||
assertEquals(new HashSet<>(foundKeys), new HashSet<>(asList(storedKeyPairs)));
|
||||
assertEquals(foundKeys.size(), COUNT_KEY_PAIRS);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = ConflictException.class)
|
||||
public void throwsConflictExceptionWhenCreatingSignatureKeyPair() throws Exception {
|
||||
final SignatureKeyPairImpl signKeyPair = newKeyPair(storedKeyPairs[0].getId());
|
||||
final SignatureKeyPairImpl signKeyPair = newKeyPair(storedKeyPairs[0].getWorkspaceId());
|
||||
|
||||
dao.create(signKeyPair);
|
||||
}
|
||||
|
||||
@Test(expectedExceptions = NotFoundException.class)
|
||||
public void throwsNoResultExceptionWhenSearchingWrongWorkspace() throws Exception {
|
||||
dao.get("unknown");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatesSignatureKeyPair() throws Exception {
|
||||
final SignatureKeyPairImpl signKeyPair = newKeyPair("id_" + 10);
|
||||
|
||||
dao.create(signKeyPair);
|
||||
|
||||
final Page<SignatureKeyPairImpl> keys = dao.getAll(COUNT_KEY_PAIRS + 1, 0);
|
||||
assertTrue(keys.getItems().contains(signKeyPair));
|
||||
assertEquals(keys.getTotalItemsCount(), COUNT_KEY_PAIRS + 1);
|
||||
final SignatureKeyPairImpl kp = dao.get(signKeyPair.getWorkspaceId());
|
||||
assertNotNull(kp);
|
||||
assertEquals(kp, signKeyPair);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expectedExceptions = NotFoundException.class)
|
||||
public void testRemovesSignatureKeyPair() throws Exception {
|
||||
final SignatureKeyPairImpl toRemove = storedKeyPairs[0];
|
||||
|
||||
dao.remove(toRemove.getId());
|
||||
dao.remove(toRemove.getWorkspaceId());
|
||||
|
||||
final Page<SignatureKeyPairImpl> keys = dao.getAll(COUNT_KEY_PAIRS, 0);
|
||||
assertFalse(keys.getItems().contains(toRemove));
|
||||
assertEquals(keys.getTotalItemsCount(), COUNT_KEY_PAIRS - 1);
|
||||
dao.get(toRemove.getWorkspaceId());
|
||||
}
|
||||
|
||||
private SignatureKeyPairImpl newKeyPair(String id) {
|
||||
private SignatureKeyPairImpl newKeyPair(String workspaceId) {
|
||||
final KeyPair pair = kpg.generateKeyPair();
|
||||
return new SignatureKeyPairImpl(id, pair.getPublic(), pair.getPrivate());
|
||||
return new SignatureKeyPairImpl(workspaceId, pair.getPublic(), pair.getPrivate());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
--
|
||||
-- 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
|
||||
--
|
||||
|
||||
-- Rename old table
|
||||
ALTER TABLE che_sign_key_pair RENAME TO che_sign_key_pair_old;
|
||||
|
||||
-- Create new key pair table
|
||||
CREATE TABLE che_sign_key_pair (
|
||||
workspace_id VARCHAR(255) NOT NULL,
|
||||
public_key BIGINT NOT NULL,
|
||||
private_key BIGINT NOT NULL,
|
||||
|
||||
PRIMARY KEY (workspace_id)
|
||||
);
|
||||
-- Constraint
|
||||
ALTER TABLE che_sign_key_pair ADD CONSTRAINT fk_sign_workspace_id FOREIGN KEY (workspace_id) REFERENCES workspace (id);
|
||||
|
||||
-- Copy data
|
||||
INSERT INTO che_sign_key_pair
|
||||
SELECT r.workspace_id, k.public_key, k.private_key
|
||||
FROM che_k8s_runtime AS r,
|
||||
(SELECT public_key, private_key FROM che_sign_key_pair_old LIMIT 1) AS k;
|
||||
|
||||
-- Cleanup
|
||||
DROP TABLE che_sign_key_pair_old CASCADE;
|
||||
Loading…
Reference in New Issue