Skip to content
This repository has been archived by the owner on Mar 17, 2021. It is now read-only.

Commit

Permalink
Bayesian compatibility and workspace cleaning with multi-tenancy (#417)
Browse files Browse the repository at this point in the history
* Adapt to the last upstream changes

* Make Bayesian plugin work with multi-tenancy (issue #376) by:
- creating a service endpoint started on wsmaster along with the
Bayesian Agent, to provide the required up-to-date token (from the user
that started the workspace)  from the machine token of the workspace
agent
- retrieving this token endpoint when starting the language server on
the workspace agent.

Signed-off-by: David Festal <dfestal@redhat.com>
  • Loading branch information
davidfestal authored Nov 13, 2017
1 parent d344377 commit dcbb673
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import com.redhat.bayesian.agent.BayesianAgent;
import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.inject.DynaModule;
import org.eclipse.che.multiuser.keycloak.token.provider.oauth.OpenShiftGitHubOAuthAuthenticator;
import org.eclipse.che.security.oauth.OAuthAuthenticator;
Expand All @@ -23,8 +21,6 @@
public class Fabric8WsMasterModule extends AbstractModule {
@Override
protected void configure() {
Multibinder<Agent> agents = Multibinder.newSetBinder(binder(), Agent.class);
agents.addBinding().to(BayesianAgent.class);
bind(org.eclipse.che.api.workspace.server.WorkspaceFilesCleaner.class)
.to(org.eclipse.che.plugin.openshift.client.OpenShiftWorkspaceFilesCleaner.class);
Multibinder<OAuthAuthenticator> oAuthAuthenticators =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@
*/
package org.eclipse.che.plugin.languageserver.bayesian.server.launcher;

import static org.slf4j.LoggerFactory.getLogger;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.redhat.che.keycloak.token.store.service.KeycloakTokenStore;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import javax.inject.Named;
import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.api.languageserver.exception.LanguageServerException;
import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncherTemplate;
import org.eclipse.che.api.languageserver.registry.DocumentFilter;
Expand All @@ -27,22 +30,27 @@
import org.eclipse.lsp4j.jsonrpc.Launcher;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageServer;
import org.slf4j.Logger;

/**
* @author Evgen Vidolob
* @author Anatolii Bazko
*/
@Singleton
public class BayesianLanguageServerLauncher extends LanguageServerLauncherTemplate {
private static final Logger LOG = getLogger(BayesianLanguageServerLauncher.class);

private static final LanguageServerDescription DESCRIPTION = createServerDescription();

private final Path launchScript;

@Inject private KeycloakTokenStore keycloakStore;
private final HttpJsonRequestFactory httpJsonFactory;
private final String apiEndpoint;

@Inject
public BayesianLanguageServerLauncher() {
public BayesianLanguageServerLauncher(
HttpJsonRequestFactory httpJsonFactory, @Named("che.api") String apiEndpoint) {
this.httpJsonFactory = httpJsonFactory;
this.apiEndpoint = apiEndpoint;
launchScript = Paths.get(System.getenv("HOME"), "che/ls-bayesian/launch.sh");
}

Expand All @@ -64,19 +72,19 @@ protected LanguageServer connectToLanguageServer(
}

protected Process startLanguageServerProcess(String projectPath) throws LanguageServerException {
String launchCommand = launchScript.toString();
String recommenderToken = null;

if (keycloakStore.hasLastToken()) {
launchCommand =
"export RECOMMENDER_API_TOKEN="
+ //
keycloakStore.getLastTokenWithoutBearer()
+ //
" && "
+ //
launchCommand;
try {
String endpoint = apiEndpoint + "/bayesian/token";
LOG.debug("Retrieving the Bayesian recommender token from API : {}", endpoint);
recommenderToken = httpJsonFactory.fromUrl(endpoint).request().asString();
} catch (Exception e) {
throw new LanguageServerException("Can't start Bayesian language server", e);
}

String launchCommand =
"export RECOMMENDER_API_TOKEN=\"" + recommenderToken + "\" && " + launchScript.toString();

ProcessBuilder processBuilder = new ProcessBuilder("/bin/bash", "-c", launchCommand);
processBuilder.redirectInput(ProcessBuilder.Redirect.PIPE);
processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.eclipse.che.api.core.rest.HttpJsonResponse;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.multiuser.keycloak.token.provider.service.KeycloakTokenProvider;
import org.eclipse.che.plugin.openshift.client.OpenshiftWorkspaceEnvironmentProvider;
import org.eclipse.che.plugin.openshift.client.exception.OpenShiftException;
Expand Down Expand Up @@ -116,28 +117,39 @@ public Fabric8WorkspaceEnvironmentProvider(
.build(CacheLoader.from(this::loadUserCheTenantData));
}

private void checkSubject(Subject subject) throws OpenShiftException {
if (subject == null) {
throw new OpenShiftException("No Subject is found to perform this action");
}
if (subject == Subject.ANONYMOUS) {
throw new OpenShiftException(
"The anonymous subject is used, and won't be able to perform this action");
}
}

@Override
public Config getWorkspacesOpenshiftConfig() throws OpenShiftException {
public Config getWorkspacesOpenshiftConfig(Subject subject) throws OpenShiftException {
if (!fabric8CheMultitenant) {
return super.getWorkspacesOpenshiftConfig();
return super.getWorkspacesOpenshiftConfig(subject);
}

String osoToken = getOpenShiftTokenForUser();
checkSubject(subject);

String osoToken = getOpenShiftTokenForUser(subject);
if (osoToken == null) {
throw new OpenShiftException(
"OSO token is null => Connecting to Openshift with default config");
throw new OpenShiftException("OSO token not found for user: " + getUserDescription(subject));
}

UserCheTenantData cheTenantData;
cheTenantData = getUserCheTenantData();
cheTenantData = getUserCheTenantData(subject);
if (cheTenantData == null) {
throw new OpenShiftException(
"User tenant data not found => Connecting to Openshift with default config");
"User tenant data not found for user: " + getUserDescription(subject));
}

return new ConfigBuilder()
.withMasterUrl(cheTenantData.getClusterUrl())
.withOauthToken(getOpenShiftTokenForUser())
.withOauthToken(osoToken)
.withNamespace(cheTenantData.getNamespace())
.withTrustCerts(true)
.build();
Expand All @@ -153,21 +165,31 @@ public Config getWorkspacesOpenshiftConfig() throws OpenShiftException {
| ConflictException
| BadRequestException
| IOException e) {
throw new RuntimeException(
"Cound not retrieve OSO token from Keycloak token: " + keycloakToken, e);
throw new RuntimeException("Cound not retrieve OSO token from Keycloak token", e);
}
}

public String getOpenShiftTokenForUser() throws OpenShiftException {
String keycloakToken = EnvironmentContext.getCurrent().getSubject().getToken();
private String getUserDescription(Subject subject) {
return subject.getUserName() + "(" + subject.getUserId() + ")";
}

public String getOpenShiftTokenForUser(Subject subject) throws OpenShiftException {

checkSubject(subject);

String keycloakToken = subject.getToken();
if (keycloakToken == null) {
throw new OpenShiftException("User Openshift token is needed but there is no current user");
throw new OpenShiftException(
"User Openshift token is needed but cannot be retrieved since there is no Keycloak token for user: "
+ getUserDescription(subject));
}
try {
return tokenCache.get(keycloakToken);
} catch (ExecutionException e) {
throw new OpenShiftException(
"Cound not retrieve OSO token from Keycloak token: " + keycloakToken, e.getCause());
"Could not retrieve OSO token from Keycloak token for user: "
+ getUserDescription(subject),
e.getCause());
}
}

Expand Down Expand Up @@ -215,8 +237,10 @@ private UserCheTenantData loadUserCheTenantData(String keycloakToken) {
throw new RuntimeException("No che namespace was found in the user tenant");
}

public UserCheTenantData getUserCheTenantData() throws OpenShiftException {
String keycloakToken = EnvironmentContext.getCurrent().getSubject().getToken();
public UserCheTenantData getUserCheTenantData(Subject subject) throws OpenShiftException {
checkSubject(subject);

String keycloakToken = subject.getToken();
if (keycloakToken == null) {
throw new OpenShiftException("User tenant data is needed but there is no current user");
}
Expand All @@ -228,6 +252,10 @@ public UserCheTenantData getUserCheTenantData() throws OpenShiftException {
}
}

public UserCheTenantData getUserCheTenantData() throws OpenShiftException {
return getUserCheTenantData(EnvironmentContext.getCurrent().getSubject());
}

private String getResponseBody(final String endpoint, final String keycloakToken)
throws ServerException, UnauthorizedException, ForbiddenException, NotFoundException,
ConflictException, BadRequestException, IOException {
Expand All @@ -241,14 +269,17 @@ private String getResponseBody(final String endpoint, final String keycloakToken
}

@Override
public String getWorkspacesOpenshiftNamespace() throws OpenShiftException {
public String getWorkspacesOpenshiftNamespace(Subject subject) throws OpenShiftException {
if (!fabric8CheMultitenant) {
return super.getWorkspacesOpenshiftNamespace();
return super.getWorkspacesOpenshiftNamespace(subject);
}

UserCheTenantData cheTenantData = getUserCheTenantData();
checkSubject(subject);

UserCheTenantData cheTenantData = getUserCheTenantData(subject);
if (cheTenantData == null) {
throw new OpenShiftException("User tenant data not found !");
throw new OpenShiftException(
"User tenant data not found for user: " + getUserDescription(subject));
}
return cheTenantData.getNamespace();
}
Expand Down
32 changes: 32 additions & 0 deletions plugins/ls-bayesian-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,42 @@
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-multibindings</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>javax.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-agent-shared</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-api-workspace</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.core</groupId>
<artifactId>che-core-commons-inject</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.che.multiuser</groupId>
<artifactId>che-multiuser-machine-authentication</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2016-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package com.redhat.bayesian.agent;

import com.google.inject.AbstractModule;
import com.google.inject.multibindings.Multibinder;
import org.eclipse.che.api.agent.shared.model.Agent;
import org.eclipse.che.inject.DynaModule;

/** @author David Festal */
@DynaModule
public class BayesianAgentModule extends AbstractModule {
@Override
protected void configure() {
Multibinder<Agent> agents = Multibinder.newSetBinder(binder(), Agent.class);
agents.addBinding().to(BayesianAgent.class);
bind(BayesianTokenProvider.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2016-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package com.redhat.bayesian.agent;

import static org.slf4j.LoggerFactory.getLogger;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.workspace.server.WorkspaceSubjectRegistry;
import org.eclipse.che.commons.subject.Subject;
import org.eclipse.che.multiuser.machine.authentication.server.MachineTokenRegistry;
import org.slf4j.Logger;

@Path("/bayesian")
@Singleton
public class BayesianTokenProvider {
private static final Logger LOG = getLogger(BayesianTokenProvider.class);

@Inject private MachineTokenRegistry machineTokenRegistry;
@Inject private WorkspaceSubjectRegistry workspaceSubjectRegistry;

@GET
@Path("/token")
@Produces(MediaType.APPLICATION_JSON)
public Response getBayesianToken(@HeaderParam(HttpHeaders.AUTHORIZATION) String machineToken)
throws NotFoundException {
String workspaceId = machineTokenRegistry.getWorkspaceId(machineToken);
LOG.debug("workspaceId for Bayesian recommender token retrieval: {}", workspaceId);
Subject workspaceStarter = workspaceSubjectRegistry.getWorkspaceStarter(workspaceId);
if (workspaceStarter == null) {
LOG.error(
"Subject that started workspace '{}' was not found during Bayesian recommender token retrieval.",
workspaceId);
throw new NotFoundException("Bayesian token not found");
}
String keycloakToken = workspaceStarter.getToken();
if (keycloakToken == null) {
LOG.error(
"Keycloak token of the subject that started workspace '{}' (user name = '{}' )was not found during Bayesian recommender token retrieval.",
workspaceId,
workspaceStarter.getUserName());
throw new NotFoundException("Bayesian token not found");
}
return Response.ok("\"" + keycloakToken + '"').build();
}
}

0 comments on commit dcbb673

Please sign in to comment.