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

Commit

Permalink
CHE-283 : Add a property to disable keycloak authentication in rh-che…
Browse files Browse the repository at this point in the history
… assembly (#174)

* First sketch of CHE-283
* Add the customized valve to enable bypassing Keycloak authentication
* Disable Keycloak by default (as requested by @l0rd) in the RH assembly
* Fix a bad bug bringing CORS errors due to duplicate headers in response
* Clean formatting-only changes
* Fix for #181

Signed-off-by: David Festal <dfestal@redhat.com>
  • Loading branch information
davidfestal committed Jul 11, 2017
1 parent b08591d commit 7ffb061
Show file tree
Hide file tree
Showing 15 changed files with 393 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
--- src/main/webapp/IDE.jsp
+++ src/main/webapp/IDE.jsp
@@ -55,7 +55,30 @@
@@ -55,7 +55,37 @@
};

</script>
+ <script type="text/javascript" language="javascript" src="/_app/keycloak/keycloak.js"></script>
+ <script>
+ window.keycloak = Keycloak({
+ url: 'https://sso.openshift.io/auth',
+ realm: 'fabric8',
+ clientId: 'openshiftio-public',
+ });
+ window.keycloak.init({ onLoad: 'check-sso', checkLoginIframe: false, }).success(function(authenticated) {
+ console.log('IDE.jsp authenticated with token:'+ window.keycloak.token);
+ }).error(function() {
+ console.log('failed to initialize');
+ });

+ // Until there's a synchronous way to call updateToken from within gwt (it's currently
+ // asynchronous), just attempt to refresh it every 5 mins.
+ setInterval(function() { window.keycloak.updateToken()
+ .success(function(refreshed){
+ console.log('IDE.js token.refresh :'+refreshed);
+ if(refreshed){
+ console.log('IDE.js setting token to'+ window.keycloak.token);
+ }
+ })
+ ; }, 300000);
+ const req = new XMLHttpRequest();
+ req.open('GET', window.IDE.config['restContext'] + '/keycloak/settings', false);
+ req.send(null); console.log('responseText = ' + req.responseText);
+ const keycloakDisabled = JSON.parse(req.responseText);
+ if (keycloakDisabled['che.keycloak.disabled'] != "true") {
+ window.keycloak = Keycloak({
+ url: 'https://sso.openshift.io/auth',
+ realm: 'fabric8',
+ clientId: 'openshiftio-public',
+ });
+ window.keycloak.init({ onLoad: 'check-sso', checkLoginIframe: false, }).success(function(authenticated) {
+ console.log('IDE.jsp authenticated with token:'+ window.keycloak.token);
+ }).error(function() {
+ console.log('failed to initialize');
+ });

+ // Until there's a synchronous way to call updateToken from within gwt (it's currently
+ // asynchronous), just attempt to refresh it every 5 mins.
+ setInterval(function() { window.keycloak.updateToken()
+ .success(function(refreshed){
+ console.log('IDE.js token.refresh :'+refreshed);
+ if(refreshed){
+ console.log('IDE.js setting token to'+ window.keycloak.token);
+ }
+ })
+ ; }, 300000);
+ }
+
+ </script>
<script type="text/javascript" language="javascript" src="/_app/browserNotSupported.js"></script>
<script type="text/javascript" language="javascript" async="true" src="/_app/_app.nocache.js"></script>
Expand Down
10 changes: 10 additions & 0 deletions builds/fabric8-che/assembly/assembly-wsagent-war/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceDirectory>${project.build.directory}/patched/src/main/webapp</warSourceDirectory>
<failOnMissingWebXml>false</failOnMissingWebXml>
<dependentWarIncludes>**,META-INF/context.xml,WEB-INF/**</dependentWarIncludes>
<dependentWarExcludes>META-INF/MANIFEST.MF,META-INF/maven/**,WEB-INF/lib/che-plugin-svn-ext-*,WEB-INF/classes/org/eclipse/che/wsagent/server/CheWsAgentServletModule.class</dependentWarExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@
import org.eclipse.che.inject.DynaModule;
import com.redhat.che.keycloak.server.KeycloakAuthenticationFilter;
import javax.inject.Singleton;
import org.eclipse.che.api.core.cors.CheCorsFilter;
import com.redhat.che.keycloak.server.KeycloakPropertiesProvider;
import org.everrest.guice.servlet.GuiceEverrestServlet;
import com.google.inject.name.Names;

/** @author andrew00x */
@DynaModule
public class RedHatCheWsAgentServletModule extends ServletModule {
@Override
protected void configureServlets() {
filter("/*").through(CheCorsFilter.class);
serveRegex("^/api((?!(/(ws|eventbus)($|/.*)))/.*)").with(GuiceEverrestServlet.class);

bind(Boolean.class).annotatedWith(Names.named("che.keycloak.disabled")).toProvider(KeycloakPropertiesProvider.class);
bind(KeycloakAuthenticationFilter.class).in(Singleton.class);
filter("/*").through(KeycloakAuthenticationFilter.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
import org.eclipse.che.commons.lang.Pair;
import org.eclipse.che.inject.DynaModule;

import com.google.inject.name.Names;
import java.net.URI;
import com.redhat.che.keycloak.server.KeycloakPropertiesProvider;
import org.eclipse.che.UriApiEndpointProvider;

import javax.inject.Named;

Expand Down Expand Up @@ -46,8 +50,8 @@ protected void configure() {
install(new org.eclipse.che.api.core.jsonrpc.impl.JsonRpcModule());
install(new org.eclipse.che.api.core.websocket.impl.WebSocketModule());

bind(Boolean.class).annotatedWith(Names.named("che.keycloak.disabled")).toProvider(KeycloakPropertiesProvider.class);
bind(HttpJsonRequestFactory.class).to(KeycloakHttpJsonRequestFactory.class);

}

//it's need for WSocketEventBusClient and in the future will be replaced with the property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,5 +234,6 @@ protected void configure() {

Multibinder<OAuthAuthenticator> oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class);
oAuthAuthenticators.addBinding().to(OpenShiftGitHubOAuthAuthenticator.class);
bind(com.redhat.che.keycloak.server.KeycloakService.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
# Endpoints for obtiaining Github / OpenShift Online tokens based on Keycloak token
che.keycloak.oso.endpoint=NULL
che.keycloak.github.endpoint=NULL
che.keycloak.disabled=true
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
--- src/app/index.module.ts
+++ src/app/index.module.ts
@@ -31,13 +31,36 @@ import {ProxySettingsConfig} from './proxy/proxy-settings.constant';
@@ -31,13 +31,59 @@
import {WorkspacesConfig} from './workspaces/workspaces-config';
import {StacksConfig} from './stacks/stacks-config';
import {DemoComponentsCtrl} from './demo-components/demo-components.controller';
Expand All @@ -17,27 +17,50 @@
'angular-websocket', 'ui.bootstrap', 'ui.codemirror', 'ngMaterial', 'ngMessages', 'angularMoment', 'angular.filter',
'ngDropdowns', 'ngLodash', 'angularCharts', 'ngClipboard', 'uuid4', 'angularFileUpload']);

+angular.element(document).ready(() => {
+ console.log('running keycloak init sequence');
+angular.element(document).ready(($injector) => {
+
+ window['_keycloak'] = Keycloak(keycloakConfig);
+ let promise = $injector.get('../wsmaster/api/keycloak/settings');
+
+ function keycloakInit(keycloakDisabled) {
+ if (keycloakDisabled) {
+ window['_keycloak'] = false;
+ angular.bootstrap(document, ['userDashboard'], {strictDi:true}); // manually bootstrap Angular
+ } else {
+ window['_keycloak'] = Keycloak(keycloakConfig);
+ window['_keycloak']
+ .init({
+ onLoad: 'login-required'
+ })
+ .success(() => {
+ angular.bootstrap(document, ['userDashboard'], {strictDi:true}); // manually bootstrap Angular
+ });
+ }
+
+ }
+
+ promise.then((resp) => {
+ console.log('resp : ' + resp.toString() );
+ if (resp['che.keycloak.disabled'] == 'true') {
+ keycloakInit(true);
+ } else {
+ keycloakInit(false);
+ }
+ }, (error) => {
+ console.log(error);
+ keycloakInit(false);
+ });
+
+ console.log('running keycloak init sequence');
+
+ window['_keycloak']
+ .init({
+ onLoad: 'login-required'
+ })
+ .success(() => {
+ angular.bootstrap(document, ['userDashboard'], {strictDi:true}); // manually bootstrap Angular
+ });
+});
+
+initModule.factory('keycloak', $window => {
+ return $window._keycloak;
+ return $window._keycloak;
+});

// add a global resolve flag on all routes (user needs to be resolved first)
initModule.config(['$routeProvider', ($routeProvider) => {
@@ -165,6 +188,42 @@ initModule.run(['$rootScope', '$location', '$routeParams', 'routingRedirect', '$
@@ -168,6 +214,44 @@
});
}]);

Expand All @@ -54,7 +77,7 @@
+ let deferred = $q.defer();
+ keycloak.updateToken(5).success(function () {
+ config.headers = config.headers || {};
+ config.headers.Authorization = 'Bearer ' + keycloak.token;
+ angular.extend(config.headers, {'Authorization': 'Bearer ' + keycloak.token});
+ console.log('injecting token : ' + config.url);
+ deferred.resolve(config);
+ }).error(function () {
Expand All @@ -68,19 +91,21 @@
+ response: (response) => {
+ console.log('RESPONSE '+response.config.url+ ' :' + JSON.stringify(response));
+ return response || $q.when(response);
+ },responseError: (rejection)=>{
+ },
+ responseError: (rejection)=>{
+ console.log('ERROR (response) : '+ JSON.stringify(rejection));
+ return $q.reject(rejection);
+ },
+ requestError: (rejection) =>{
+ console.log('ERROR (request) : '+ JSON.stringify(rejection))
+ console.log('ERROR (request) : '+ JSON.stringify(rejection));
+ return $q.reject(rejection);
+ }
+
+ }
+});

// add interceptors
initModule.factory('ETagInterceptor', ($window, $cookies, $q) => {
@@ -355,6 +414,7 @@ initModule.constant('userDashboardConfig', {
@@ -358,6 +442,7 @@
});

initModule.config(['$routeProvider', '$locationProvider', '$httpProvider', ($routeProvider, $locationProvider, $httpProvider) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,15 @@
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.connector.Request;
import org.apache.catalina.realm.GenericPrincipal;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.keycloak.KeycloakSecurityContext;
Expand All @@ -40,9 +45,21 @@ public class UserAuthValve extends KeycloakAuthenticatorValve {
private static final Log LOG = LogFactory.getLog(UserAuthValve.class);
private static final String USER_VALIDATOR_ENDPOINT = "http://che-host:8080/api/token/user";
private static final String AUTHORIZATION_HEADER = "Authorization";

private static final String KEYCLOAK_SETTINGS_ENDPOINT = "http://che-host:8080/api/keycloak/settings";
private static final Pattern DISABLED_SETTING_PATTERN = Pattern.compile(".*\"che\\.keycloak\\.disabled\":\"([^ \"]+)\".*");

private boolean keycloakDisabledRetrieved = false;
private boolean keycloakDisabled = false;

@Override
public boolean authenticate(Request request, HttpServletResponse response) throws IOException {
if (isKeycloakDisabled()) {
LOG.info("Keycloak is disabled => Bypassing authentification");
GenericPrincipal genericPrincipal = new GenericPrincipal("developer", "developer", Arrays.asList("developer"));
request.setUserPrincipal(genericPrincipal);
request.setAuthType(HttpServletRequest.BASIC_AUTH);
return true;
}
if (super.authenticate(request, response)) {
String auth = getToken(request);
if (auth != null && userMatches(auth)) {
Expand Down Expand Up @@ -94,4 +111,38 @@ private String getToken(Request request) {
(KeycloakSecurityContext)request.getAttribute(KeycloakSecurityContext.class.getName());
return ksc.getTokenString();
}

public boolean isKeycloakDisabled() {
retrieveKeycloakDisabledSetting();
return keycloakDisabled;
}

public void retrieveKeycloakDisabledSetting() {
if (! keycloakDisabledRetrieved) {
URL url;
HttpURLConnection conn;
try {
url = new URL(KEYCLOAK_SETTINGS_ENDPOINT);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
response.append(line);
}
if (LOG.isInfoEnabled()) {
LOG.info("KeycloakSettings = " + response);
}
Matcher matcher = DISABLED_SETTING_PATTERN.matcher(response.toString());
if (matcher.matches()) {
String value = matcher.group(1);
keycloakDisabled = "true".equals(value);
keycloakDisabledRetrieved = true;
}
} catch (IOException e) {
LOG.error("Exception during Keycloak settings retrieval", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.List;

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.rest.AsyncRequest;
import org.eclipse.che.ide.rest.AsyncRequestFactory;
Expand All @@ -17,16 +18,26 @@
*/
@Singleton
public class KeycloakAsyncRequestFactory extends AsyncRequestFactory {
private final DtoFactory dtoFactory;
private final DtoFactory dtoFactory;
private AppContext appContext;
private boolean keycloakDisabled = false;

@Inject
public KeycloakAsyncRequestFactory(DtoFactory dtoFactory) {
public KeycloakAsyncRequestFactory(DtoFactory dtoFactory,
AppContext appContext) {
super(dtoFactory);
this.dtoFactory = dtoFactory;
this.appContext = appContext;
this.keycloakDisabled = isKeycloakDisabled(appContext.getMasterEndpoint());
}

@Override
protected AsyncRequest doCreateRequest(RequestBuilder.Method method, String url, Object dtoBody, boolean async) {
Preconditions.checkNotNull(method, "Request method should not be a null");

if (keycloakDisabled) {
return super.doCreateRequest(method, url, dtoBody, async);
}

AsyncRequest asyncRequest = new KeycloakAsyncRequest(method, url, async);
if (dtoBody != null) {
Expand Down Expand Up @@ -59,4 +70,21 @@ public static native String getBearerToken() /*-{
//$wnd.keycloak.updateToken(10);
return "Bearer " + $wnd.keycloak.token;
}-*/;

public static native void log(String message) /*-{
console.log(message);
}-*/;

public static native boolean isKeycloakDisabled(String theMasterEndpoint) /*-{
var myReq = new XMLHttpRequest();
myReq.open('GET', '' + theMasterEndpoint + '/keycloak/settings', false);
myReq.send(null);
var keycloakDisabled = JSON.parse(myReq.responseText);
if (keycloakDisabled['che.keycloak.disabled'] != "true") {
return false;
} else {
return true;
}
}-*/;

}
Loading

0 comments on commit 7ffb061

Please sign in to comment.