Skip to content

Instantly share code, notes, and snippets.

@aaronanderson
Last active August 14, 2016 08:04
Show Gist options
  • Save aaronanderson/10d47edacd2f20d197117e3e0c04c30d to your computer and use it in GitHub Desktop.
Save aaronanderson/10d47edacd2f20d197117e3e0c04c30d to your computer and use it in GitHub Desktop.
E(FX)clipse Eclipse access secure P2 update site with custom certificate and oauth2/oidc authentication
public class Application {
@Inject
IEclipseContext econtext;
// @Override
@PostConstruct
protected void jfxStart(IApplicationContext context, javafx.application.Application jfxApplication, Stage primaryStage) {
performUpdateCheck();
ClientService clientService = ContextInjectionFactory.make(ClientService.class, econtext);
econtext.set(ClientService.class, clientService);
}
public static class StdProgressReporter implements ProgressReporter{
@Override
public void taskUnitsChanged(String taskId, int totalUnits) {
System.out.format("Task units changed %s %d\n", taskId, totalUnits);
}
@Override
public void taskStart(String taskId, String parentTaskId, String taskName, int totalUnits, boolean cancelable) {
System.out.format("Task Start %s %s %s %d %s\n", taskId, parentTaskId, taskName, totalUnits, cancelable);
}
@Override
public void taskEnd(String taskId, boolean canceled) {
System.out.format("Task End %s %s \n", taskId, canceled);
}
@Override
public void progress(String taskId, String message, int unitsDone) {
System.out.format("Progress %s %s %d\n", taskId, message, unitsDone);
}
}
public void performUpdateCheck() {
// https://blog.codecentric.de/en/2015/04/add-p2-update-functionality-to-an-efxclipse-application-eclipse-rcp-cookbook/
CancelableOperation<Optional<UpdatePlan>> check = updateService.checkUpdate(new StdProgressReporter());
check.onComplete((updatePlan) -> {
System.out.format("Update Complete\n");
if (updatePlan.isPresent()) {
System.out.format("Update available!\n");
CancelableOperation result = updatePlan.get().runUpdate(new StdProgressReporter());
result.onComplete((r) -> {
});
} else {
System.out.format("Nothing to update\n");
}
});
check.onCancel(() -> System.out.format("Operation cancelled\n"));
check.onException(t -> {
String message = t.getStatus().getMessage();
System.out.format("Update Error %s\n", message);
});
System.out.println("MainController - end updates");
}
package com.mercer.cpsg.workspace.app;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.json.JsonObject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.eclipse.fx.core.event.EventBus;
import org.eclipse.fx.core.log.Log;
import org.eclipse.fx.core.log.Logger;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
@Singleton
public class ClientService {
@Inject
@Log
Logger logger;
@Inject
private EventBus eventBus;
private final AtomicReference<String> refreshRef = new AtomicReference<>();
private final ReadWriteLock tokenLock = new ReentrantReadWriteLock();
private OIDCToken oidcToken;
private String refreshToken;
private Client client;
private String idpURL;
private String clientId;
private String clientSecret;
@PostConstruct
public void init() {
boolean devMode = Boolean.parseBoolean(System.getProperty("app.devmode", "false"));
if (devMode) {
idpURL = "https://ORG.okta.com";
clientId = "X";
clientSecret = "X";
} else {
idpURL = "https://ORG.okta.com";
clientId = "X";
clientSecret = "X";
}
client = buildClient();
}
public String getCurrentRefreshToken() throws IOException {
if (refreshToken == null) {
try {
String newRefreshToken = getRefreshToken();
if (refreshRef.compareAndSet(null, newRefreshToken)) {
refreshToken = newRefreshToken;
}
} catch (IOException e) {
logger.error(" Security Setup error", e);
}
}
return refreshToken;
}
public String getRefreshToken() throws IOException {
//private
}
public void setRefreshToken(String token) throws IOException {
//private
}
public String getAccessToken() {
if (refreshToken == null) {
try {
refreshToken = getCurrentRefreshToken();
} catch (IOException e) {
logger.error(" auth setup error", e);
}
}
tokenLock.readLock().lock();
try {
if ((oidcToken == null || LocalDateTime.now().isAfter(oidcToken.timeoutTime.plusSeconds(30))) && refreshToken != null) {
tokenLock.readLock().unlock();
tokenLock.writeLock().lock();
try {
if ((oidcToken == null || LocalDateTime.now().isAfter(oidcToken.timeoutTime.plusSeconds(30))) && refreshToken != null) {
oidcToken = retrieveAccessToken().orElse(null);
}
} finally {
tokenLock.writeLock().unlock();
tokenLock.readLock().lock();
}
}
return oidcToken != null ? oidcToken.token : null;
} finally {
tokenLock.readLock().unlock();
}
}
public boolean connectionAvailable() {
return true;
}
public WebTarget getTarget(String url) {
return client.target(url);
}
private Client buildClient() {
SSLContext sslContext = null;
try {
sslContext = getSSLContext();
} catch (Throwable t) {
logger.error("TLS setup error ", t);
return null;
}
ResteasyClientBuilder builder = new ResteasyClientBuilder();
builder.hostnameVerification(ResteasyClientBuilder.HostnameVerificationPolicy.ANY);
Client client = builder.connectionPoolSize(200).sslContext(sslContext).build();
OIDCAuthenticator auth = new OIDCAuthenticator(OIDCAuthenticator.Mode.Bearer, this::getAccessToken);
return client.register(auth);
}
public boolean testAuthenticate(String account, String password) {
Optional<JsonObject> result = oidcAuthenticate(account, password, "openid");
return result.isPresent() && result.get().containsKey("access_token");
}
public Optional<OIDCToken> retrieveAccessToken() {
Client oauthClient = ClientBuilder.newBuilder().build();
try {
oauthClient.register(new OIDCAuthenticator(OIDCAuthenticator.Mode.Basic, () -> String.format("%s:%s", clientId, clientSecret)));
Form form = new Form();
form.param("grant_type", "refresh_token");
form.param("refresh_token", getCurrentRefreshToken());
form.param("scope", "openid profile email groups offline_access");
Response response = oauthClient.target(idpURL).path("/oauth2/v1/token").request().post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
JsonObject payload = response.readEntity(JsonObject.class);
String token = payload.getString("access_token");
long expiry = payload.getJsonNumber("expires_in").longValue();
return Optional.of(new OIDCToken(token, LocalDateTime.now().plusSeconds(expiry)));
} else {
logger.error(String.format("Invalid OIDC response %d %s", response.getStatus(), response.readEntity(String.class)));
}
} catch (Throwable t) {
logger.error("OIDC ", t);
} finally {
oauthClient.close();
}
return Optional.empty();
}
public boolean retrieveTokens(String account, String password) {
Optional<JsonObject> result = oidcAuthenticate(account, password, "openid profile email groups offline_access");
if (result.isPresent() && result.get().containsKey("refresh_token")) {
refreshToken = result.get().getString("refresh_token");
try {
setRefreshToken(refreshToken);
} catch (Throwable t) {
logger.error("", t);
}
if (result.get().containsKey("access_token")) {
tokenLock.writeLock().lock();
try {
String token = result.get().getString("access_token");
long expiry = result.get().getJsonNumber("expires_in").longValue();
oidcToken = new OIDCToken(token, LocalDateTime.now().plusSeconds(expiry));
} finally {
tokenLock.writeLock().unlock();
}
}
return true;
}
return false;
}
public Optional<JsonObject> oidcAuthenticate(String account, String password, String scope) {
Client oauthClient = ClientBuilder.newBuilder().build();
try {
oauthClient.register(new OIDCAuthenticator(OIDCAuthenticator.Mode.Basic, () -> String.format("%s:%s", clientId, clientSecret)));
Form form = new Form();
form.param("grant_type", "password");
form.param("username", account);
form.param("password", password);
form.param("scope", scope);
Response response = oauthClient.target(idpURL).path("/oauth2/v1/token").request().post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE));
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
return Optional.of(response.readEntity(JsonObject.class));
} else {
logger.error(String.format("Invalid OIDC response %d %s", response.getStatus(), response.readEntity(String.class)));
}
} catch (Throwable t) {
logger.error("OIDC ", t);
} finally {
oauthClient.close();
}
return Optional.empty();
}
public static class OIDCAuthenticator implements ClientRequestFilter {
private final Mode mode;
private final Supplier<String> tokenGenerator;
public OIDCAuthenticator(Mode mode, Supplier<String> tokenGenerator) {
this.mode = mode;
this.tokenGenerator = tokenGenerator;
}
public void filter(ClientRequestContext requestContext) throws IOException {
try {
String token = tokenGenerator.get();
if (token == null || token.isEmpty()) {
throw new IOException("Empty auth token");
}
final String authorization = mode + " " + (mode == Mode.Basic ? Base64.getEncoder().encodeToString(token.getBytes()) : token);
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
headers.add("Authorization", authorization);
} catch (IllegalArgumentException ex) {
throw new IOException(ex);
}
}
public static enum Mode {
Basic, Bearer;
}
}
public static class OIDCToken {
final String token;
final LocalDateTime timeoutTime;
public OIDCToken(String token, LocalDateTime timeoutTime) {
this.token = token;
this.timeoutTime = timeoutTime;
}
public String getToken() {
return token;
}
}
public static SSLContext getSSLContext() throws Exception {
InputStream fis = Thread.currentThread().getContextClassLoader().getResourceAsStream("swarm.cer");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(fis);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
keyStore.setCertificateEntry("Integration_Library", cert);
tmf.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);
return sslContext;
}
}
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Mercer CPSG App
Bundle-SymbolicName: com.mercer.cpsg.app; singleton:=true
Bundle-Version: 1.0.0.final
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.fx.ui.workbench.fx,
org.eclipse.e4.ui.model.workbench,
org.eclipse.e4.core.services,
org.eclipse.e4.core.di,
org.eclipse.e4.ui.di,
org.eclipse.e4.core.di.extensions,
org.eclipse.fx.ui.theme,
org.eclipse.fx.ui.di,
org.eclipse.e4.core.contexts,
org.eclipse.fx.core.databinding,
org.eclipse.fx.ui.databinding,
org.eclipse.core.databinding,
org.eclipse.core.databinding.observable,
org.eclipse.core.databinding.property,
org.eclipse.e4.ui.workbench,
org.eclipse.e4.ui.services,
org.eclipse.fx.ui.services,
org.eclipse.fx.ui.controls;bundle-version="2.4.0",
org.eclipse.fx.osgi.util;bundle-version="2.4.0",
org.eclipse.fx.core,
org.eclipse.fx.core.di,
org.eclipse.core.runtime,
org.eclipse.ecf,
org.eclipse.ecf.filetransfer,
org.eclipse.ecf.provider.filetransfer,
org.eclipse.ecf.provider.filetransfer.httpclient4,
org.apache.httpcomponents.httpclient,
org.apache.httpcomponents.httpcore
Service-Component: OSGI-INF/services/theme-default.xml
Import-Package: javax.inject;version="1.0.0"
Bundle-ClassPath: .,
lib/
Bundle-Vendor: Mercer CPSG
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
<extension
id="application"
point="org.eclipse.core.runtime.applications">
<application
thread="any">
<run
class="org.eclipse.fx.ui.di.DIApplication">
<parameter name="mainClass" value="com.mercer.cpsg.app.Application" />
</run>
</application>
</extension>
<extension
id="product"
point="org.eclipse.core.runtime.products">
<product name="Mercer CPSG App" application="com.mercer.cpsg.app.application" >
<property name="appName" value="Mercer CPSGApp" />
</product>
</extension>
<extension
point="org.eclipse.ecf.provider.filetransfer.retrieveFileTransferProtocolFactory">
<retrieveFileTransferProtocolFactory
class="com.mercer.cpsg.workspace.app.filetransfer.SecureHttpClientRetrieveFileTransferFactory"
protocol="https"
priority="40">
</retrieveFileTransferProtocolFactory>
</extension>
<extension
point="org.eclipse.ecf.provider.filetransfer.browseFileTransferProtocolFactory">
<browseFileTransferProtocolFactory
class="com.mercer.cpsg.workspace.app.filetransfer.SecureHttpClientBrowseFileTransferFactory"
protocol="https"
priority="40">
</browseFileTransferProtocolFactory>
</extension>
</plugin>
package com.mercer.cpsg.workspace.app.filetransfer;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.http.impl.client.DefaultHttpClient;
import org.eclipse.core.runtime.Assert;
import org.eclipse.ecf.core.identity.IDFactory;
import org.eclipse.ecf.core.identity.Namespace;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.core.util.Proxy;
import org.eclipse.ecf.filetransfer.IRemoteFileSystemListener;
import org.eclipse.ecf.filetransfer.IRemoteFileSystemRequest;
import org.eclipse.ecf.filetransfer.RemoteFileSystemException;
import org.eclipse.ecf.filetransfer.identity.IFileID;
import org.eclipse.ecf.filetransfer.service.IRemoteFileSystemBrowser;
import org.eclipse.ecf.filetransfer.service.IRemoteFileSystemBrowserFactory;
import org.eclipse.ecf.provider.filetransfer.httpclient4.HttpClientFileSystemBrowser;
import org.eclipse.ecf.provider.filetransfer.identity.FileTransferNamespace;
import org.eclipse.osgi.util.NLS;
public class SecureHttpClientBrowseFileTransferFactory implements IRemoteFileSystemBrowserFactory {
public IRemoteFileSystemBrowser newInstance() {
return new IRemoteFileSystemBrowser() {
private Proxy proxy;
private IConnectContext connectContext;
public Namespace getBrowseNamespace() {
return IDFactory.getDefault().getNamespaceByName(FileTransferNamespace.PROTOCOL);
}
public IRemoteFileSystemRequest sendBrowseRequest(IFileID directoryOrFileId, IRemoteFileSystemListener listener) throws RemoteFileSystemException {
Assert.isNotNull(directoryOrFileId);
Assert.isNotNull(listener);
URL url;
try {
url = directoryOrFileId.getURL();
} catch (final MalformedURLException e) {
throw new RemoteFileSystemException(NLS.bind("Exception creating URL for {0}", directoryOrFileId)); //$NON-NLS-1$
}
DefaultHttpClient client = new DefaultHttpClient();
HttpClientFileSystemBrowser browser = new HttpClientFileSystemBrowser(client, directoryOrFileId, listener, url, connectContext, proxy);
SecureHttpClientRetrieveFileTransferFactory.configureHttpClient(client);
return browser.sendBrowseRequest();
}
public void setConnectContextForAuthentication(IConnectContext connectContext) {
this.connectContext = connectContext;
}
public void setProxy(Proxy proxy) {
this.proxy = proxy;
}
public Object getAdapter(Class adapter) {
return null;
}
};
}
}
package com.mercer.cpsg.workspace.app.filetransfer;
import java.io.IOException;
import java.security.Principal;
import javax.net.ssl.SSLContext;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthSchemeFactory;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.BasicUserPrincipal;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.MalformedChallengeException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransfer;
import org.eclipse.ecf.filetransfer.service.IRetrieveFileTransferFactory;
import org.eclipse.ecf.provider.filetransfer.httpclient4.HttpClientRetrieveFileTransfer;
import org.eclipse.fx.ui.di.DIApplication;
import org.osgi.framework.FrameworkUtil;
import com.mercer.cpsg.workspace.app.ClientService;
/*
ECF uses an old deprecated version of Apache HTTPClient. The authentication functionality available is limited to HttpClient v3.x ntlm and basic authentication with username/password callbacks and no premptive support.
Proxy support is also available. Instead of plugging into the old HTTP Client/ECF framework we will just let HttpClientRetrieveFileTransfer setup the client and then override the necessary configurations to
a) trust a specific self signed certificate
b) insert an oauth 2 access token for SSO
*/
public class SecureHttpClientRetrieveFileTransferFactory implements IRetrieveFileTransferFactory {
public IRetrieveFileTransfer newInstance() {
DefaultHttpClient client = new DefaultHttpClient(new SingleClientConnManager());
HttpClientRetrieveFileTransfer transfer = new HttpClientRetrieveFileTransfer(client);
configureHttpClient(client);
return transfer;
}
// Override the schemas. The ECFHttpClientSecureProtocolSocketFactory will not be used but the ISocketEventSource and ISocketListener
// references seem to be only used for tracing and monitoring. This is also done here
// http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/providers/bundles/org.eclipse.ecf.provider.filetransfer.httpclient4/src/org/eclipse/ecf/provider/filetransfer/httpclient4/SNIAwareHttpClient.java
public static void configureHttpClient(DefaultHttpClient client) {
SSLContext sslContext = null;
try {
sslContext = ClientService.getSSLContext();
} catch (Exception e) {
e.printStackTrace();
}
SSLSocketFactory factory = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
final SchemeRegistry registry = client.getConnectionManager().getSchemeRegistry();
registry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
registry.register(new Scheme("https", 443, factory));
registry.register(new Scheme("https", 8443, factory));
/* List authpref = new ArrayList(1);
* authpref.add("oauth2");
* client.getParams().setParameter(AuthPNames.PROXY_AUTH_PREF, authpref);
* client.getParams().setParameter(AuthPNames.TARGET_AUTH_PREF, authpref);
* client.getAuthSchemes().register("oauth2", new OAuth2SchemaProvider()); */
BasicCredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope(null, -1, null), new OAuthCredentials("OAuth App Name"));
client.setCredentialsProvider(credsProvider);
client.addRequestInterceptor(new OAuth2Interceptor(), 0);
}
// http://stackoverflow.com/a/29012443
static class OAuth2Interceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
// If no auth scheme available yet, try to initialize it
// preemptively
if (authState.getAuthScheme() == null) {
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
authState.update(new OAuth2Scheme(), creds);
}
}
}
public static class OAuthCredentials implements Credentials {
private ClientService clientService;
private BasicUserPrincipal principal;
public OAuthCredentials(String name) {
this.principal = new BasicUserPrincipal(name);
}
@Override
public String getPassword() {
return null;
}
@Override
public Principal getUserPrincipal() {
return principal;
}
public String getAccessToken() {
if (clientService == null) {
try {
// IEclipseContext injection in application class is in the di bundle context and not the actual application bundle. Make sure to load the same bundle.
IEclipseContext serviceContext = EclipseContextFactory.getServiceContext(FrameworkUtil.getBundle(DIApplication.class).getBundleContext());
clientService = serviceContext.get(ClientService.class);
} catch (Exception e) {
e.printStackTrace();
}
}
return clientService.getAccessToken();
}
}
public static class OAuth2SchemaProvider implements AuthSchemeFactory {
@Override
public AuthScheme newInstance(HttpParams arg0) {
return new OAuth2Scheme();
}
}
public static class OAuth2Scheme implements AuthScheme {
@Override
public Header authenticate(Credentials credentials, HttpRequest request) throws AuthenticationException {
OAuthCredentials oauthCredentials = (OAuthCredentials) credentials;
String token = oauthCredentials.getAccessToken();
if (token == null) {
throw new AuthenticationException("OAuth2 access token not available");
}
return new BasicHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
}
@Override
public String getParameter(String arg0) {
return null;
}
@Override
public String getRealm() {
// TODO Auto-generated method stub
return null;
}
@Override
public String getSchemeName() {
return "oauth2";
}
@Override
public boolean isComplete() {
return true;
}
@Override
public boolean isConnectionBased() {
return false;
}
@Override
public void processChallenge(Header arg0) throws MalformedChallengeException {
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment