From d4f03cbc4ae73327877a8473aadd44e963443c8d Mon Sep 17 00:00:00 2001 From: Sergii Kabashniuk Date: Mon, 18 Sep 2017 18:42:49 +0300 Subject: [PATCH] Ability to authenticate Oauth flow (#6326) * Add keycloak token to oauth authenticate call * fixup! Add keycloak token to oauth authenticate call * fixup! Add keycloak token to oauth authenticate call * Fix dashboard build * fixup! Add keycloak token to oauth authenticate call * fixup! Add keycloak token to oauth authenticate call * Add security token for websocket url (#6319) * Add security token for websocket url Signed-off-by: Vitalii Parfonov * Fix failed test (#6325) Signed-off-by: Vitalii Parfonov --- .../che/api/deploy/MachineAuthModule.java | 31 ++--- .../oauth/DefaultOAuthAuthenticatorImpl.java | 10 +- .../websocket/impl/WebSocketInitializer.java | 31 ++++- .../che/security/oauth/JsOAuthWindow.java | 33 ++++- .../security/oauth/SecurityTokenProvider.java | 27 ++++ .../org/eclipse/che/ide/Commons.gwt.xml | 1 + .../impl/WebSocketInitializerTest.java | 47 +++++-- .../che-multiuser-keycloak-ide/pom.xml | 4 + .../che/multiuser/keycloak/ide/Keycloak.java | 1 + .../keycloak/ide/KeycloakAsyncRequest.java | 66 +++------ .../ide/KeycloakAsyncRequestFactory.java | 52 +------- .../keycloak/ide/KeycloakProvider.java | 125 ++++++++++++++++++ .../ide/KeycloakSecurityTokenProvider.java | 24 ++++ .../ide/inject/KeycloakAuthGinModule.java | 5 + .../server/deploy/KeycloakServletModule.java | 4 +- .../github/ide/GitHubSshKeyUploader.java | 9 +- .../GitHubAuthenticatorImpl.java | 12 +- .../client/GitHubHostingService.java | 8 +- .../client/vcs/hosting/ServiceUtil.java | 8 +- 19 files changed, 351 insertions(+), 147 deletions(-) create mode 100644 ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/SecurityTokenProvider.java create mode 100644 multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakProvider.java create mode 100644 multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakSecurityTokenProvider.java diff --git a/assembly-multiuser/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/MachineAuthModule.java b/assembly-multiuser/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/MachineAuthModule.java index 97c6a18e5f..7b609567fc 100644 --- a/assembly-multiuser/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/MachineAuthModule.java +++ b/assembly-multiuser/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/MachineAuthModule.java @@ -11,18 +11,10 @@ package org.eclipse.che.api.deploy; import com.google.inject.AbstractModule; -import org.eclipse.che.api.environment.server.MachineLinksInjector; import org.eclipse.che.api.workspace.server.WorkspaceServiceLinksInjector; -import org.eclipse.che.commons.auth.token.HeaderRequestTokenExtractor; +import org.eclipse.che.commons.auth.token.ChainedTokenExtractor; import org.eclipse.che.commons.auth.token.RequestTokenExtractor; import org.eclipse.che.inject.DynaModule; -import org.eclipse.che.multiuser.machine.authentication.server.AuthWsAgentHealthChecker; -import org.eclipse.che.multiuser.machine.authentication.server.MachineAuthLinksInjector; -import org.eclipse.che.multiuser.machine.authentication.server.MachineSessionInvalidator; -import org.eclipse.che.multiuser.machine.authentication.server.MachineTokenPermissionsFilter; -import org.eclipse.che.multiuser.machine.authentication.server.MachineTokenRegistry; -import org.eclipse.che.multiuser.machine.authentication.server.MachineTokenService; -import org.eclipse.che.multiuser.machine.authentication.server.WorkspaceServiceAuthLinksInjector; import org.eclipse.che.multiuser.machine.authentication.server.interceptor.InterceptorModule; /** @@ -32,18 +24,23 @@ import org.eclipse.che.multiuser.machine.authentication.server.interceptor.Inter */ @DynaModule public class MachineAuthModule extends AbstractModule { + @Override protected void configure() { install(new InterceptorModule()); - bind(MachineLinksInjector.class).to(MachineAuthLinksInjector.class); bind(org.eclipse.che.api.agent.server.WsAgentHealthChecker.class) - .to(AuthWsAgentHealthChecker.class); - bind(MachineTokenPermissionsFilter.class); - bind(MachineTokenService.class); - bind(MachineTokenRegistry.class); - bind(MachineSessionInvalidator.class); - bind(RequestTokenExtractor.class).to(HeaderRequestTokenExtractor.class); - bind(WorkspaceServiceLinksInjector.class).to(WorkspaceServiceAuthLinksInjector.class); + .to(org.eclipse.che.multiuser.machine.authentication.server.AuthWsAgentHealthChecker.class); + bind( + org.eclipse.che.multiuser.machine.authentication.server.MachineTokenPermissionsFilter + .class); + bind(org.eclipse.che.multiuser.machine.authentication.server.MachineTokenService.class); + bind(org.eclipse.che.multiuser.machine.authentication.server.MachineTokenRegistry.class); + bind(org.eclipse.che.multiuser.machine.authentication.server.MachineSessionInvalidator.class); + bind(RequestTokenExtractor.class).to(ChainedTokenExtractor.class); + bind(WorkspaceServiceLinksInjector.class) + .to( + org.eclipse.che.multiuser.machine.authentication.server + .WorkspaceServiceAuthLinksInjector.class); bind(org.eclipse.che.api.environment.server.MachineInstanceProvider.class) .to(org.eclipse.che.plugin.docker.machine.AuthMachineProviderImpl.class); } diff --git a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/oauth/DefaultOAuthAuthenticatorImpl.java b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/oauth/DefaultOAuthAuthenticatorImpl.java index 2a78a6cad2..6780a46f4c 100644 --- a/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/oauth/DefaultOAuthAuthenticatorImpl.java +++ b/ide/che-core-ide-app/src/main/java/org/eclipse/che/ide/oauth/DefaultOAuthAuthenticatorImpl.java @@ -23,6 +23,7 @@ import org.eclipse.che.ide.api.oauth.OAuth2Authenticator; import org.eclipse.che.security.oauth.JsOAuthWindow; import org.eclipse.che.security.oauth.OAuthCallback; import org.eclipse.che.security.oauth.OAuthStatus; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** * Default implementation of authenticator, used when no provider-specific one is present. @@ -34,13 +35,18 @@ public class DefaultOAuthAuthenticatorImpl implements OAuth2Authenticator, OAuth private final DialogFactory dialogFactory; private final CoreLocalizationConstant localizationConstant; + private final SecurityTokenProvider provider; private String authenticationUrl; @Inject public DefaultOAuthAuthenticatorImpl( - DialogFactory dialogFactory, CoreLocalizationConstant localizationConstant) { + DialogFactory dialogFactory, + CoreLocalizationConstant localizationConstant, + SecurityTokenProvider provider) { + this.dialogFactory = dialogFactory; this.localizationConstant = localizationConstant; + this.provider = provider; } @Override @@ -96,7 +102,7 @@ public class DefaultOAuthAuthenticatorImpl implements OAuth2Authenticator, OAuth private void showAuthWindow() { JsOAuthWindow authWindow; - authWindow = new JsOAuthWindow(authenticationUrl, "error.url", 500, 980, this); + authWindow = new JsOAuthWindow(authenticationUrl, "error.url", 500, 980, this, provider); authWindow.loginWithOAuth(); } } diff --git a/ide/commons-gwt/src/main/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializer.java b/ide/commons-gwt/src/main/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializer.java index 5af16c78be..c33bdaa43f 100644 --- a/ide/commons-gwt/src/main/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializer.java +++ b/ide/commons-gwt/src/main/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializer.java @@ -15,7 +15,10 @@ import static java.util.Collections.emptySet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; +import org.eclipse.che.api.promises.client.Operation; +import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.ide.util.loging.Log; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** * Contain all routines related to a web socket connection initialization @@ -28,17 +31,20 @@ public class WebSocketInitializer { private final WebSocketPropertyManager propertyManager; private final WebSocketActionManager actionManager; private final UrlResolver urlResolver; + private SecurityTokenProvider securityTokenProvider; @Inject public WebSocketInitializer( WebSocketConnectionManager connectionManager, WebSocketPropertyManager propertyManager, WebSocketActionManager actionManager, - UrlResolver urlResolver) { + UrlResolver urlResolver, + SecurityTokenProvider securityTokenProvider) { this.connectionManager = connectionManager; this.propertyManager = propertyManager; this.actionManager = actionManager; this.urlResolver = urlResolver; + this.securityTokenProvider = securityTokenProvider; } /** @@ -61,16 +67,27 @@ public class WebSocketInitializer { * @param initActions actions to be performed each time the connection is established */ public void initialize(String endpointId, String url, Set initActions) { - Log.debug(getClass(), "Initializing with url: " + url); + securityTokenProvider + .getSecurityToken() + .then( + new Operation() { + @Override + public void apply(String token) throws OperationException { + String separator = url.contains("?") ? "&" : "?"; + final String secureUrl = url + separator + "token=" + token; - urlResolver.setMapping(endpointId, url); + Log.debug(getClass(), "Initializing with secureUrl: " + secureUrl); - propertyManager.initializeConnection(url); + urlResolver.setMapping(endpointId, secureUrl); - actionManager.setOnEstablishActions(url, initActions); + propertyManager.initializeConnection(secureUrl); - connectionManager.initializeConnection(url); - connectionManager.establishConnection(url); + actionManager.setOnEstablishActions(secureUrl, initActions); + + connectionManager.initializeConnection(secureUrl); + connectionManager.establishConnection(secureUrl); + } + }); } /** diff --git a/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/JsOAuthWindow.java b/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/JsOAuthWindow.java index 1bd0114633..7a4019d56a 100644 --- a/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/JsOAuthWindow.java +++ b/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/JsOAuthWindow.java @@ -11,6 +11,10 @@ package org.eclipse.che.security.oauth; import com.google.gwt.user.client.Window; +import org.eclipse.che.api.promises.client.Operation; +import org.eclipse.che.api.promises.client.OperationException; +import org.eclipse.che.api.promises.client.PromiseError; +import org.eclipse.che.ide.util.loging.Log; /** @author Vladislav Zhukovskii */ public class JsOAuthWindow { @@ -19,16 +23,23 @@ public class JsOAuthWindow { private OAuthStatus authStatus; private int popupHeight; private int popupWidth; + private SecurityTokenProvider provider; private int clientHeight; private int clientWidth; private OAuthCallback callback; public JsOAuthWindow( - String authUrl, String errUrl, int popupHeight, int popupWidth, OAuthCallback callback) { + String authUrl, + String errUrl, + int popupHeight, + int popupWidth, + OAuthCallback callback, + SecurityTokenProvider provider) { this.authUrl = authUrl; this.errUrl = errUrl; this.popupHeight = popupHeight; this.popupWidth = popupWidth; + this.provider = provider; this.clientHeight = Window.getClientHeight(); this.clientWidth = Window.getClientWidth(); this.callback = callback; @@ -46,7 +57,25 @@ public class JsOAuthWindow { } public void loginWithOAuth() { - loginWithOAuth(authUrl, errUrl, popupHeight, popupWidth, clientHeight, clientWidth); + provider + .getSecurityToken() + .then( + new Operation() { + @Override + public void apply(String arg) throws OperationException { + if (arg != null) { + authUrl = authUrl + "&token=" + arg; + } + loginWithOAuth(authUrl, errUrl, popupHeight, popupWidth, clientHeight, clientWidth); + } + }) + .catchError( + new Operation() { + @Override + public void apply(PromiseError arg) throws OperationException { + Log.error(getClass(), arg); + } + }); } private native void loginWithOAuth( diff --git a/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/SecurityTokenProvider.java b/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/SecurityTokenProvider.java new file mode 100644 index 0000000000..d20a049982 --- /dev/null +++ b/ide/commons-gwt/src/main/java/org/eclipse/che/security/oauth/SecurityTokenProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2012-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 org.eclipse.che.security.oauth; + +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseProvider; + +/** Provider of tokens that needed to authenticate requests. */ +@Singleton +public class SecurityTokenProvider { + + @Inject PromiseProvider promiseProvider; + + public Promise getSecurityToken() { + return promiseProvider.resolve(null); + } +} diff --git a/ide/commons-gwt/src/main/resources/org/eclipse/che/ide/Commons.gwt.xml b/ide/commons-gwt/src/main/resources/org/eclipse/che/ide/Commons.gwt.xml index 00e702ad9d..ccdc90cecb 100644 --- a/ide/commons-gwt/src/main/resources/org/eclipse/che/ide/Commons.gwt.xml +++ b/ide/commons-gwt/src/main/resources/org/eclipse/che/ide/Commons.gwt.xml @@ -16,6 +16,7 @@ + diff --git a/ide/commons-gwt/src/test/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializerTest.java b/ide/commons-gwt/src/test/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializerTest.java index a0aed484dc..8d91f571ab 100644 --- a/ide/commons-gwt/src/test/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializerTest.java +++ b/ide/commons-gwt/src/test/java/org/eclipse/che/ide/websocket/impl/WebSocketInitializerTest.java @@ -13,10 +13,16 @@ package org.eclipse.che.ide.websocket.impl; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import org.eclipse.che.api.promises.client.Operation; +import org.eclipse.che.api.promises.client.OperationException; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.security.oauth.SecurityTokenProvider; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; @@ -32,40 +38,53 @@ public class WebSocketInitializerTest { @Mock private WebSocketPropertyManager propertyManager; @Mock private UrlResolver urlResolver; @Mock private WebSocketActionManager webSocketActionManager; + @Mock private SecurityTokenProvider securityTokenProvider; + @Mock private Promise promise; + @Captor private ArgumentCaptor> operation; @InjectMocks private WebSocketInitializer initializer; @Before - public void setUp() throws Exception {} + public void setUp() throws Exception { + when(securityTokenProvider.getSecurityToken()).thenReturn(promise); + } @After public void tearDown() throws Exception {} @Test - public void shouldSetUrlMappingOnInitialize() { - initializer.initialize("id", "url"); + public void shouldSetUrlMappingOnInitialize() throws OperationException { - verify(urlResolver).setMapping("id", "url"); + initializer.initialize("id", "http://test.com"); + verify(promise).then(operation.capture()); + operation.getValue().apply("token"); + verify(securityTokenProvider).getSecurityToken(); + verify(urlResolver).setMapping("id", "http://test.com?token=token"); } @Test - public void shouldRunConnectionManagerInitializeConnectionOnInitialize() { - initializer.initialize("id", "url"); - - verify(connectionManager).initializeConnection("url"); + public void shouldRunConnectionManagerInitializeConnectionOnInitialize() + throws OperationException { + initializer.initialize("id", "http://test.com"); + verify(promise).then(operation.capture()); + operation.getValue().apply("token"); + verify(securityTokenProvider).getSecurityToken(); + verify(connectionManager).initializeConnection("http://test.com?token=token"); } @Test - public void shouldRunPropertyManagerInitializeConnectionOnInitialize() { + public void shouldRunPropertyManagerInitializeConnectionOnInitialize() throws OperationException { initializer.initialize("id", "url"); - - verify(propertyManager).initializeConnection("url"); + verify(promise).then(operation.capture()); + operation.getValue().apply("token"); + verify(propertyManager).initializeConnection("url?token=token"); } @Test - public void shouldRunEstablishConnectionOnInitialize() { + public void shouldRunEstablishConnectionOnInitialize() throws OperationException { initializer.initialize("id", "url"); - - verify(connectionManager).establishConnection("url"); + verify(promise).then(operation.capture()); + operation.getValue().apply("token"); + verify(connectionManager).establishConnection("url?token=token"); } @Test diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/pom.xml b/multiuser/keycloak/che-multiuser-keycloak-ide/pom.xml index 08d807e73a..68e68266b4 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-ide/pom.xml +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/pom.xml @@ -31,6 +31,10 @@ com.google.inject guice + + javax.inject + javax.inject + org.eclipse.che.core che-core-commons-gwt diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/Keycloak.java b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/Keycloak.java index 195fa6d514..f5dd45c06b 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/Keycloak.java +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/Keycloak.java @@ -38,6 +38,7 @@ public final class Keycloak extends JavaScriptObject { console.log('[Keycloak] Failed to initialize Keycloak'); reject(); }); + console.log('[Keycloak] Initializing complete'); } catch (ex) { console.log('[Keycloak] Failed to initialize Keycloak with exception: ', ex); reject(); diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequest.java b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequest.java index 04bddb4299..856adf1412 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequest.java +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequest.java @@ -22,16 +22,16 @@ import org.eclipse.che.ide.util.loging.Log; /** KeycloakAsyncRequests */ public class KeycloakAsyncRequest extends AsyncRequest { - private Promise keycloakPromise; + private KeycloakProvider keycloakProvider; public KeycloakAsyncRequest( - Promise keycloakPromise, RequestBuilder.Method method, String url, boolean async) { + KeycloakProvider keycloakProvider, RequestBuilder.Method method, String url, boolean async) { super(method, url, async); - this.keycloakPromise = keycloakPromise; + this.keycloakProvider = keycloakProvider; } - private void addAuthorizationHeader(Keycloak keycloak) { - header(HTTPHeader.AUTHORIZATION, "Bearer " + keycloak.getToken()); + private void addAuthorizationHeader(String keycloakToken) { + header(HTTPHeader.AUTHORIZATION, "Bearer " + keycloakToken); } private static interface Sender { @@ -39,47 +39,21 @@ public class KeycloakAsyncRequest extends AsyncRequest { } private Promise doAfterKeycloakInitAndUpdate(Sender sender) { - return keycloakPromise.thenPromise( - new Function>() { - @Override - public Promise apply(Keycloak keycloak) { - Log.debug(getClass(), "Keycloak initialized with token: ", keycloak.getToken()); - try { - return keycloak - .updateToken(5) - .thenPromise( - new Function>() { - @Override - public Promise apply(Boolean refreshed) { - if (refreshed) { - Log.debug( - getClass(), - "Keycloak updated token before sending the request `", - KeycloakAsyncRequest.this.requestBuilder.getUrl(), - "`. New token is : ", - keycloak.getToken()); - } else { - Log.debug( - getClass(), - "Keycloak didn't need to update token before sending the request `", - KeycloakAsyncRequest.this.requestBuilder.getUrl(), - "`"); - } - addAuthorizationHeader(keycloak); - try { - return sender.doSend(); - } catch (Throwable t) { - Log.error(getClass(), t); - throw t; - } - } - }); - } catch (Throwable t) { - Log.error(getClass(), t); - throw t; - } - } - }); + return keycloakProvider + .getUpdatedToken(5) + .thenPromise( + new Function>() { + @Override + public Promise apply(String keycloakToken) { + addAuthorizationHeader(keycloakToken); + try { + return sender.doSend(); + } catch (Throwable t) { + Log.error(getClass(), t); + throw t; + } + } + }); } @Override diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequestFactory.java b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequestFactory.java index 2e3aab183a..5b0dff9c24 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequestFactory.java +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakAsyncRequestFactory.java @@ -10,30 +10,18 @@ */ package org.eclipse.che.multiuser.keycloak.ide; -import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING; -import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; -import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; - -import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.core.client.ScriptInjector; import com.google.gwt.http.client.RequestBuilder; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import java.util.List; -import java.util.Map; -import org.eclipse.che.api.promises.client.Promise; -import org.eclipse.che.api.promises.client.callback.CallbackPromiseHelper; import org.eclipse.che.ide.MimeType; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.dto.DtoFactory; -import org.eclipse.che.ide.json.JsonHelper; import org.eclipse.che.ide.rest.AsyncRequest; import org.eclipse.che.ide.rest.HTTPHeader; -import org.eclipse.che.ide.util.loging.Log; -import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants; import org.eclipse.che.multiuser.machine.authentication.ide.MachineAsyncRequestFactory; import org.eclipse.che.multiuser.machine.authentication.ide.MachineTokenServiceClient; @@ -42,50 +30,18 @@ import org.eclipse.che.multiuser.machine.authentication.ide.MachineTokenServiceC public class KeycloakAsyncRequestFactory extends MachineAsyncRequestFactory { private final DtoFactory dtoFactory; - private Promise keycloak; + private KeycloakProvider keycloakProvider; @Inject public KeycloakAsyncRequestFactory( + KeycloakProvider keycloakProvider, DtoFactory dtoFactory, Provider machineTokenServiceProvider, AppContext appContext, EventBus eventBus) { super(dtoFactory, machineTokenServiceProvider, appContext, eventBus); this.dtoFactory = dtoFactory; - String keycloakSettings = - getKeycloakSettings(KeycloakConstants.getEndpoint(appContext.getMasterEndpoint())); - Map settings = JsonHelper.toMap(keycloakSettings); - Log.info(getClass(), "Keycloak settings: ", settings); - - keycloak = - CallbackPromiseHelper.createFromCallback( - new CallbackPromiseHelper.Call() { - @Override - public void makeCall(final Callback callback) { - ScriptInjector.fromUrl( - settings.get(AUTH_SERVER_URL_SETTING) + "/js/keycloak.js") - .setCallback( - new Callback() { - @Override - public void onSuccess(Void result) { - callback.onSuccess(null); - } - - @Override - public void onFailure(Exception reason) { - callback.onFailure(reason); - } - }) - .setWindow(getWindow()) - .inject(); - } - }) - .thenPromise( - (v) -> - Keycloak.init( - settings.get(AUTH_SERVER_URL_SETTING), - settings.get(REALM_SETTING), - settings.get(CLIENT_ID_SETTING))); + this.keycloakProvider = keycloakProvider; } @Override @@ -93,7 +49,7 @@ public class KeycloakAsyncRequestFactory extends MachineAsyncRequestFactory { RequestBuilder.Method method, String url, Object dtoBody, boolean async) { AsyncRequest request = super.doCreateRequest(method, url, dtoBody, async); if (!isWsAgentRequest(url)) { - AsyncRequest asyncRequest = new KeycloakAsyncRequest(keycloak, method, url, async); + AsyncRequest asyncRequest = new KeycloakAsyncRequest(keycloakProvider, method, url, async); if (dtoBody != null) { if (dtoBody instanceof List) { asyncRequest.data(dtoFactory.toJson((List) dtoBody)); diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakProvider.java b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakProvider.java new file mode 100644 index 0000000000..5d51de4ed8 --- /dev/null +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakProvider.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2012-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 org.eclipse.che.multiuser.keycloak.ide; + +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.AUTH_SERVER_URL_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.CLIENT_ID_SETTING; +import static org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants.REALM_SETTING; + +import com.google.gwt.core.client.Callback; +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.ScriptInjector; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.Map; +import org.eclipse.che.api.promises.client.Function; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.api.promises.client.PromiseProvider; +import org.eclipse.che.api.promises.client.callback.CallbackPromiseHelper; +import org.eclipse.che.ide.api.app.AppContext; +import org.eclipse.che.ide.json.JsonHelper; +import org.eclipse.che.ide.util.loging.Log; +import org.eclipse.che.multiuser.keycloak.shared.KeycloakConstants; + +/** KeycloakProvider */ +@Singleton +public class KeycloakProvider { + private AppContext appContext; + private boolean keycloakDisabled = false; + private Promise keycloak; + + @Inject + public KeycloakProvider(AppContext appContext, PromiseProvider promiseProvider) { + this.appContext = appContext; + String keycloakSettings = + getKeycloakSettings(KeycloakConstants.getEndpoint(appContext.getMasterEndpoint())); + Map settings = JsonHelper.toMap(keycloakSettings); + Log.info(getClass(), "Keycloak settings: ", settings); + + keycloak = + CallbackPromiseHelper.createFromCallback( + new CallbackPromiseHelper.Call() { + @Override + public void makeCall(final Callback callback) { + ScriptInjector.fromUrl( + settings.get(AUTH_SERVER_URL_SETTING) + "/js/keycloak.js") + .setCallback( + new Callback() { + @Override + public void onSuccess(Void result) { + callback.onSuccess(null); + } + + @Override + public void onFailure(Exception reason) { + callback.onFailure(reason); + } + }) + .setWindow(getWindow()) + .inject(); + } + }) + .thenPromise( + (v) -> + Keycloak.init( + settings.get(AUTH_SERVER_URL_SETTING), + settings.get(REALM_SETTING), + settings.get(CLIENT_ID_SETTING))); + Log.info(getClass(), "Keycloak init complete: ", this); + } + + public static native String getKeycloakSettings(String keycloakSettingsEndpoint) /*-{ + var myReq = new XMLHttpRequest(); + myReq.open('GET', '' + keycloakSettingsEndpoint, false); + myReq.send(null); + return myReq.responseText; + }-*/; + + public static native JavaScriptObject getWindow() /*-{ + return $wnd; + }-*/; + + public Promise getKeycloak() { + return keycloak; + } + + public Promise getUpdatedToken(int minValidity) { + return keycloak.thenPromise( + new Function>() { + @Override + public Promise apply(Keycloak keycloak) { + Log.debug(getClass(), "Keycloak initialized with token: ", keycloak.getToken()); + try { + return keycloak + .updateToken(minValidity) + .then( + new Function() { + @Override + public String apply(Boolean refreshed) { + if (refreshed) { + Log.debug( + getClass(), + "Keycloak updated token. New token is : ", + keycloak.getToken()); + } else { + Log.debug(getClass(), "Keycloak didn't need to update token."); + } + return keycloak.getToken(); + } + }); + } catch (Throwable t) { + Log.error(getClass(), t); + throw t; + } + } + }); + } +} diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakSecurityTokenProvider.java b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakSecurityTokenProvider.java new file mode 100644 index 0000000000..d28823bab4 --- /dev/null +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/KeycloakSecurityTokenProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012-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 org.eclipse.che.multiuser.keycloak.ide; + +import javax.inject.Inject; +import org.eclipse.che.api.promises.client.Promise; +import org.eclipse.che.security.oauth.SecurityTokenProvider; + +public class KeycloakSecurityTokenProvider extends SecurityTokenProvider { + @Inject KeycloakProvider keycloakProvider; + + @Override + public Promise getSecurityToken() { + return keycloakProvider.getUpdatedToken(5); + } +} diff --git a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/inject/KeycloakAuthGinModule.java b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/inject/KeycloakAuthGinModule.java index 6426f6222d..a28ce6d4e8 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/inject/KeycloakAuthGinModule.java +++ b/multiuser/keycloak/che-multiuser-keycloak-ide/src/main/java/org/eclipse/che/multiuser/keycloak/ide/inject/KeycloakAuthGinModule.java @@ -13,6 +13,9 @@ package org.eclipse.che.multiuser.keycloak.ide.inject; import com.google.gwt.inject.client.AbstractGinModule; import org.eclipse.che.ide.api.extension.ExtensionGinModule; import org.eclipse.che.ide.rest.AsyncRequestFactory; +import org.eclipse.che.multiuser.keycloak.ide.KeycloakProvider; +import org.eclipse.che.multiuser.keycloak.ide.KeycloakSecurityTokenProvider; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** KeycloakAuthGinModule */ @ExtensionGinModule @@ -20,7 +23,9 @@ public class KeycloakAuthGinModule extends AbstractGinModule { @Override public void configure() { + bind(KeycloakProvider.class).asEagerSingleton(); bind(AsyncRequestFactory.class) .to(org.eclipse.che.multiuser.keycloak.ide.KeycloakAsyncRequestFactory.class); + bind(SecurityTokenProvider.class).to(KeycloakSecurityTokenProvider.class); } } diff --git a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java index f35d3c4aee..d8ccd77278 100644 --- a/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java +++ b/multiuser/keycloak/che-multiuser-keycloak-server/src/main/java/org/eclipse/che/multiuser/keycloak/server/deploy/KeycloakServletModule.java @@ -22,10 +22,10 @@ public class KeycloakServletModule extends ServletModule { // Not contains '/websocket', /docs/ (for swagger) and not ends with '/ws' or '/eventbus' or '/settings/' or '/api/system/state' or '/api/stack/[^/]+/icon/' filterRegex( - "^(?!.*(/websocket/?|/docs/))(?!.*(/ws/?|/eventbus/?|/settings/?|/api/system/state/?|/api/stack/[^/]+/icon/?)$).*") + "^(?!.*(/websocket/?|/docs/))(?!.*(/ws/?|/eventbus/?|/settings/?|/api/oauth/callback/?|/api/system/state/?|/api/stack/[^/]+/icon/?)$).*") .through(KeycloakAuthenticationFilter.class); filterRegex( - "^(?!.*(/websocket/?|/docs/))(?!.*(/ws/?|/eventbus/?|/settings/?|/api/system/state/?|/api/stack/[^/]+/icon/?)$).*") + "^(?!.*(/websocket/?|/docs/))(?!.*(/ws/?|/eventbus/?|/settings/?|/api/oauth/callback/?|/api/system/state/?|/api/stack/[^/]+/icon/?)$).*") .through(KeycloakEnvironmentInitalizationFilter.class); } } diff --git a/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/GitHubSshKeyUploader.java b/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/GitHubSshKeyUploader.java index 807636b145..57c5e55538 100644 --- a/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/GitHubSshKeyUploader.java +++ b/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/GitHubSshKeyUploader.java @@ -29,6 +29,7 @@ import org.eclipse.che.plugin.ssh.key.client.SshKeyUploader; import org.eclipse.che.security.oauth.JsOAuthWindow; import org.eclipse.che.security.oauth.OAuthCallback; import org.eclipse.che.security.oauth.OAuthStatus; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** * Uploads SSH keys for github.com. @@ -45,6 +46,7 @@ public class GitHubSshKeyUploader implements SshKeyUploader, OAuthCallback { private final ProductInfoDataProvider productInfoDataProvider; private final DialogFactory dialogFactory; private final AppContext appContext; + private final SecurityTokenProvider securityTokenProvider; private AsyncCallback callback; private String userId; @@ -56,7 +58,8 @@ public class GitHubSshKeyUploader implements SshKeyUploader, OAuthCallback { NotificationManager notificationManager, ProductInfoDataProvider productInfoDataProvider, DialogFactory dialogFactory, - AppContext appContext) { + AppContext appContext, + SecurityTokenProvider securityTokenProvider) { this.gitHubService = gitHubService; this.baseUrl = appContext.getMasterEndpoint(); this.constant = constant; @@ -64,6 +67,7 @@ public class GitHubSshKeyUploader implements SshKeyUploader, OAuthCallback { this.productInfoDataProvider = productInfoDataProvider; this.dialogFactory = dialogFactory; this.appContext = appContext; + this.securityTokenProvider = securityTokenProvider; } /** {@inheritDoc} */ @@ -124,7 +128,8 @@ public class GitHubSshKeyUploader implements SshKeyUploader, OAuthCallback { + Window.Location.getHost() + "/ws/" + appContext.getWorkspace().getConfig().getName(); - JsOAuthWindow authWindow = new JsOAuthWindow(authUrl, "error.url", 500, 980, this); + JsOAuthWindow authWindow = + new JsOAuthWindow(authUrl, "error.url", 500, 980, this, securityTokenProvider); authWindow.loginWithOAuth(); } diff --git a/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/authenticator/GitHubAuthenticatorImpl.java b/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/authenticator/GitHubAuthenticatorImpl.java index 2672fddd32..fb59b5c434 100644 --- a/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/authenticator/GitHubAuthenticatorImpl.java +++ b/plugins/plugin-github/che-plugin-github-ide/src/main/java/org/eclipse/che/plugin/github/ide/authenticator/GitHubAuthenticatorImpl.java @@ -38,6 +38,7 @@ import org.eclipse.che.plugin.ssh.key.client.manage.SshKeyManagerPresenter; import org.eclipse.che.security.oauth.JsOAuthWindow; import org.eclipse.che.security.oauth.OAuthCallback; import org.eclipse.che.security.oauth.OAuthStatus; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** @author Roman Nikitenko */ public class GitHubAuthenticatorImpl @@ -55,6 +56,7 @@ public class GitHubAuthenticatorImpl private final GitHubLocalizationConstant locale; private final String baseUrl; private final AppContext appContext; + private final SecurityTokenProvider securityTokenPærovider; private String authenticationUrl; @Inject @@ -65,10 +67,12 @@ public class GitHubAuthenticatorImpl DialogFactory dialogFactory, GitHubLocalizationConstant locale, NotificationManager notificationManager, - AppContext appContext) { + AppContext appContext, + SecurityTokenProvider securityTokenPærovider) { this.registry = registry; this.sshServiceClient = sshServiceClient; this.view = view; + this.securityTokenPærovider = securityTokenPærovider; this.view.setDelegate(this); this.locale = locale; this.baseUrl = appContext.getMasterEndpoint(); @@ -125,9 +129,11 @@ public class GitHubAuthenticatorImpl private void showAuthWindow() { JsOAuthWindow authWindow; if (authenticationUrl == null) { - authWindow = new JsOAuthWindow(getAuthUrl(), "error.url", 500, 980, this); + authWindow = + new JsOAuthWindow(getAuthUrl(), "error.url", 500, 980, this, securityTokenPærovider); } else { - authWindow = new JsOAuthWindow(authenticationUrl, "error.url", 500, 980, this); + authWindow = + new JsOAuthWindow(authenticationUrl, "error.url", 500, 980, this, securityTokenPærovider); } authWindow.loginWithOAuth(); } diff --git a/plugins/plugin-github/che-plugin-github-pullrequest/src/main/java/org/eclipse/che/plugin/pullrequest/client/GitHubHostingService.java b/plugins/plugin-github/che-plugin-github-pullrequest/src/main/java/org/eclipse/che/plugin/pullrequest/client/GitHubHostingService.java index 1267660ff3..b2d1549a3b 100644 --- a/plugins/plugin-github/che-plugin-github-pullrequest/src/main/java/org/eclipse/che/plugin/pullrequest/client/GitHubHostingService.java +++ b/plugins/plugin-github/che-plugin-github-pullrequest/src/main/java/org/eclipse/che/plugin/pullrequest/client/GitHubHostingService.java @@ -48,6 +48,7 @@ import org.eclipse.che.plugin.pullrequest.client.vcs.hosting.VcsHostingService; import org.eclipse.che.plugin.pullrequest.shared.dto.HostUser; import org.eclipse.che.plugin.pullrequest.shared.dto.PullRequest; import org.eclipse.che.plugin.pullrequest.shared.dto.Repository; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** * {@link VcsHostingService} implementation for GitHub. @@ -74,6 +75,7 @@ public class GitHubHostingService implements VcsHostingService { private final GitHubClientService gitHubClientService; private final HostingServiceTemplates templates; private final String baseUrl; + private final SecurityTokenProvider securityTokenProvider; @Inject public GitHubHostingService( @@ -81,12 +83,14 @@ public class GitHubHostingService implements VcsHostingService { @NotNull final AppContext appContext, @NotNull final DtoFactory dtoFactory, @NotNull final GitHubClientService gitHubClientService, - @NotNull final GitHubTemplates templates) { + @NotNull final GitHubTemplates templates, + SecurityTokenProvider securityTokenProvider) { this.appContext = appContext; this.dtoFactory = dtoFactory; this.gitHubClientService = gitHubClientService; this.templates = templates; this.baseUrl = baseUrl; + this.securityTokenProvider = securityTokenProvider; } @Override @@ -512,7 +516,7 @@ public class GitHubHostingService implements VcsHostingService { + Window.Location.getHost() + "/ws/" + workspace.getConfig().getName(); - return ServiceUtil.performWindowAuth(this, authUrl); + return ServiceUtil.performWindowAuth(this, authUrl, securityTokenProvider); } @Override diff --git a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java index 6ba7872e28..80896b6846 100644 --- a/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java +++ b/plugins/plugin-pullrequest-parent/che-plugin-pullrequest-ide/src/main/java/org/eclipse/che/plugin/pullrequest/client/vcs/hosting/ServiceUtil.java @@ -22,6 +22,7 @@ import org.eclipse.che.plugin.pullrequest.shared.dto.HostUser; import org.eclipse.che.security.oauth.JsOAuthWindow; import org.eclipse.che.security.oauth.OAuthCallback; import org.eclipse.che.security.oauth.OAuthStatus; +import org.eclipse.che.security.oauth.SecurityTokenProvider; /** * Utils for {@link VcsHostingService} implementations. @@ -38,7 +39,9 @@ public final class ServiceUtil { * @return the promise which resolves authorized user or rejects with an error */ public static Promise performWindowAuth( - final VcsHostingService service, final String authUrl) { + final VcsHostingService service, + final String authUrl, + final SecurityTokenProvider securityTokenProvider) { final Executor.ExecutorBody exBody = new Executor.ExecutorBody() { @Override @@ -69,7 +72,8 @@ public final class ServiceUtil { } }); } - }) + }, + securityTokenProvider) .loginWithOAuth(); } };