/**
* Copyright 2014 Microsoft Open Technologies Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoftopentechnologies.intellij.helpers.aadauth;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.microsoftopentechnologies.intellij.helpers.UIHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class BrowserLauncher {
private String url;
private String redirectUrl;
private String callbackUrl;
private String windowTitle;
private Project project;
private static URLClassLoader loader = null;
public BrowserLauncher(
String url,
String redirectUrl,
String callbackUrl,
String windowTitle,
Project project) {
this.url = url;
this.redirectUrl = redirectUrl;
this.callbackUrl = callbackUrl;
this.windowTitle = windowTitle;
this.project = project;
}
public void browse() {
LauncherTask task = new LauncherTask(project, windowTitle, true);
task.queue();
}
private class LauncherTask extends Task.Modal {
public LauncherTask(@Nullable Project project, @NotNull String title, boolean canBeCancelled) {
super(project, title, canBeCancelled);
}
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
try {
progressIndicator.setIndeterminate(true);
// download the browser app jar
progressIndicator.setText("Loading authentication components...");
File appJar = ADJarLoader.load();
// popup auth UI
progressIndicator.setText("Signing in...");
launch(appJar);
} catch (ExecutionException e) {
reportError(e);
} catch (MalformedURLException e) {
reportError(e);
} catch (ClassNotFoundException e) {
reportError(e);
} catch (NoSuchMethodException e) {
reportError(e);
} catch (InvocationTargetException e) {
reportError(e);
} catch (IllegalAccessException e) {
reportError(e);
} catch (IOException e) {
reportError(e);
} catch (InterruptedException e) {
reportError(e);
}
}
private void launch(File appJar) throws NoSuchMethodException, InterruptedException, IOException, IllegalAccessException, InvocationTargetException, ClassNotFoundException {
String osName = System.getProperty("os.name").toLowerCase();
boolean isMac = osName.contains("mac");
boolean isLinux = osName.contains("linux");
// Launch the browser in-proc on Windows and out-proc on Mac and Linux.
// We do this because:
// [1] On OSX, in order for SWT to work the -XstartOnFirstThread option must
// have been passed to the VM which unfortunately is not passed to IJ/AS
// when it is invoked. So we launch a new instance of the JVM with this
// option set.
// [2] On Linux, right now, launching SWT in-proc seems to cause intermittent
// crashes. So till we know why that's happening (will be fixed in a future
// SWT release perhaps?) we'll launch out-proc which appears to work well.
//
// You might wonder why we don't just launch out-proc on Windows as well. Turns
// out, caching of AD auth session cookies are per-process. This means that
// subsequent runs of the auth flow in the browser in the same process will
// cause it to reuse session cookies which saves the user from having to enter
// credentials over and over again.
if(isMac || isLinux) {
launchExternalProcess(appJar, isMac);
} else {
launchInvoke(appJar);
}
}
private void launchExternalProcess(File appJar, boolean isMac) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InterruptedException {
List<String> args = new ArrayList<String>();
// fetch path to the currently running JVM
File javaHome = new File(System.getProperty("java.home"));
File javaExecutable = new File(javaHome, "bin" + File.separator + "java");
args.add(javaExecutable.getAbsolutePath());
if(isMac) {
// swt on mac requires this argument in order for the swt dispatch
// loop to be running on the UI thread
args.add("-XstartOnFirstThread");
}
args.add("-cp");
args.add(appJar.getAbsolutePath());
args.add("com.microsoftopentechnologies.adinteractiveauth.Program");
args.add(url);
args.add(redirectUrl);
args.add(callbackUrl);
args.add(windowTitle);
// process should exit after sign in is complete
args.add("true");
ProcessBuilder pb = new ProcessBuilder(args);
pb.start();
}
private void launchInvoke(File appJar) throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
if(loader == null) {
loader = new URLClassLoader(new URL[] {
new URL("file:///" + appJar.getPath())
}, BrowserLauncher.class.getClassLoader());
}
Class<?> program = loader.loadClass("com.microsoftopentechnologies.adinteractiveauth.Program");
final Method main = program.getDeclaredMethod("main", String[].class);
final String[] args = new String[] {
url, redirectUrl, callbackUrl, windowTitle
};
main.invoke(null, (Object) args);
}
private void reportError(Throwable err) {
UIHelper.showException("An error occurred while loading authentication components.", err);
}
}
}