Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/shared/Core/CommandContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public CommandContext()
{
FileSystem = new WindowsFileSystem();
Environment = new WindowsEnvironment(FileSystem);
SessionManager = new WindowsSessionManager(Environment, FileSystem);
SessionManager = new WindowsSessionManager(Trace, Environment, FileSystem);
ProcessManager = new WindowsProcessManager(Trace2);
Terminal = new WindowsTerminal(Trace, Trace2);
string gitPath = GetGitPath(Environment, FileSystem, Trace);
Expand All @@ -120,7 +120,7 @@ public CommandContext()
{
FileSystem = new MacOSFileSystem();
Environment = new MacOSEnvironment(FileSystem);
SessionManager = new MacOSSessionManager(Environment, FileSystem);
SessionManager = new MacOSSessionManager(Trace, Environment, FileSystem);
ProcessManager = new ProcessManager(Trace2);
Terminal = new MacOSTerminal(Trace, Trace2);
string gitPath = GetGitPath(Environment, FileSystem, Trace);
Expand All @@ -137,7 +137,7 @@ public CommandContext()
{
FileSystem = new LinuxFileSystem();
Environment = new PosixEnvironment(FileSystem);
SessionManager = new LinuxSessionManager(Environment, FileSystem);
SessionManager = new LinuxSessionManager(Trace, Environment, FileSystem);
ProcessManager = new ProcessManager(Trace2);
Terminal = new LinuxTerminal(Trace, Trace2);
string gitPath = GetGitPath(Environment, FileSystem, Trace);
Expand Down
6 changes: 5 additions & 1 deletion src/shared/Core/ISessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,17 @@ public static void OpenBrowser(this ISessionManager sm, string url)

public abstract class SessionManager : ISessionManager
{
protected ITrace Trace { get; }
protected IEnvironment Environment { get; }
protected IFileSystem FileSystem { get; }

protected SessionManager(IEnvironment env, IFileSystem fs)
protected SessionManager(ITrace trace, IEnvironment env, IFileSystem fs)
{
EnsureArgument.NotNull(trace, nameof(trace));
EnsureArgument.NotNull(env, nameof(env));
EnsureArgument.NotNull(fs, nameof(fs));

Trace = trace;
Environment = env;
FileSystem = fs;
}
Expand All @@ -69,6 +72,7 @@ public void OpenBrowser(Uri uri)

protected virtual void OpenBrowserInternal(string url)
{
Trace.WriteLine("Opening browser using framework shell-execute: " + url);
var psi = new ProcessStartInfo(url) { UseShellExecute = true };
Process.Start(psi);
}
Expand Down
106 changes: 79 additions & 27 deletions src/shared/Core/Interop/Linux/LinuxSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class LinuxSessionManager : PosixSessionManager
{
private bool? _isWebBrowserAvailable;

public LinuxSessionManager(IEnvironment env, IFileSystem fs) : base(env, fs)
public LinuxSessionManager(ITrace trace, IEnvironment env, IFileSystem fs) : base(trace, env, fs)
{
PlatformUtils.EnsureLinux();
}
Expand Down Expand Up @@ -41,6 +41,8 @@ protected override void OpenBrowserInternal(string url)
throw new Exception("Failed to locate a utility to launch the default web browser.");
}

Trace.WriteLine($"Opening browser using '{shellExecPath}: {url}");

var psi = new ProcessStartInfo(shellExecPath, url)
{
RedirectStandardOutput = true,
Expand All @@ -53,44 +55,94 @@ protected override void OpenBrowserInternal(string url)

private bool GetWebBrowserAvailable()
{
// We need a shell execute handler to be able to launch to browser
if (!TryGetShellExecuteHandler(Environment, out _))
{
Trace.WriteLine("Could not locate a shell execute handler for Linux - browser is not available.");
return false;
}

// If this is a Windows Subsystem for Linux distribution we may
// be able to launch the web browser of the host Windows OS.
// be able to launch the web browser of the host Windows OS, but
// there are further checks to do on the Windows host's session.
//
// If we are in Windows logon session 0 then the user can never interact,
// even in the WinSta0 window station. This is typical when SSH-ing into a
// Windows 10+ machine using the default OpenSSH Server configuration,
// which runs in the 'services' session 0.
//
// If we're in any other session, and in the WinSta0 window station then
// the user can possibly interact. However, since it's hard to determine
// the window station from PowerShell cmdlets (we'd need to write P/Invoke
// code and that's just messy and too many levels of indirection quite
// frankly!) we just assume any non session 0 is interactive.
//
// This assumption doesn't hold true if the user has changed the user that
// the OpenSSH Server service runs as (not a built-in NT service) *AND*
// they've SSH-ed into the Windows host (and then started a WSL shell).
// This feels like a very small subset of users...
//
if (WslUtils.IsWslDistribution(Environment, FileSystem, out _))
{
// We need a shell execute handler to be able to launch to browser
if (!TryGetShellExecuteHandler(Environment, out _))
if (WslUtils.GetWindowsSessionId(FileSystem) == 0)
{
Trace.WriteLine("This is a WSL distribution, but Windows session 0 was detected - browser is not available.");
return false;
}

//
// If we are in Windows logon session 0 then the user can never interact,
// even in the WinSta0 window station. This is typical when SSH-ing into a
// Windows 10+ machine using the default OpenSSH Server configuration,
// which runs in the 'services' session 0.
//
// If we're in any other session, and in the WinSta0 window station then
// the user can possibly interact. However, since it's hard to determine
// the window station from PowerShell cmdlets (we'd need to write P/Invoke
// code and that's just messy and too many levels of indirection quite
// frankly!) we just assume any non session 0 is interactive.
//
// This assumption doesn't hold true if the user has changed the user that
// the OpenSSH Server service runs as (not a built-in NT service) *AND*
// they've SSH-ed into the Windows host (and then started a WSL shell).
// This feels like a very small subset of users...
//
if (WslUtils.GetWindowsSessionId(FileSystem) == 0)
// Not on session 0 - we assume the user can interact with browser on Windows.
Trace.WriteLine("This is a WSL distribution - browser is available.");
return true;
}

//
// We may also be able to launch a browser if we're inside a Visual Studio Code remote session.
// VSCode overrides the BROWSER environment variable to a script that allows the user to open
// the browser on their client machine.
//
// Even though we can start a browser, one piece of critical functionality we need is the ability
// to have that browser be able to connect back to GCM over localhost. There are several types
// of VSCode remote session, and only some of them automatically forward ports in such a way that
// the client browser can automatically connect back to GCM over localhost.
//
// * SSH [OK]
// Connection over SSH to a remote machine.
//
// * Dev Containers [OK]
// Connection to a container.
//
// * Dev Tunnels [Not OK - forwarded ports not accessible on the client via localhost]
// Connection to a remote machine over the Internet using Microsoft Dev Tunnels.
//
// * WSL [Ignored - already handled above]
//
if (Environment.Variables.ContainsKey("VSCODE_IPC_HOOK_CLI") &&
Environment.Variables.ContainsKey("BROWSER"))
{
// Looking for SSH_CONNECTION tells us we're connected via SSH.
// HOWEVER, we may also see SSH_CONNECTION in a Dev Tunnel session if the tunnel server
// process was started within an SSH session (and the SSH_CONNECTION environment variable
// was inherited).
// We therefore check for the absence of the SSH_TTY variable, which gets unset
// in Dev Tunnel sessions but is always still set in regular SSH sessions.
if (Environment.Variables.ContainsKey("SSH_CONNECTION") &&
!Environment.Variables.ContainsKey("SSH_TTY"))
{
return false;
Trace.WriteLine("VSCode (Remote SSH) detected - browser is available.");
return true;
}

// If we are not in session 0, or we cannot get the Windows session ID,
// assume that we *CAN* launch the browser so that users are never blocked.
return true;
if (Environment.Variables.ContainsKey("REMOTE_CONTAINERS"))
{
Trace.WriteLine("VSCode (Dev Containers) detected - browser is available.");
return true;
}

Trace.WriteLine("VSCode (Remote Tunnel) detected - browser is not available.");
return false;
}

// We require an interactive desktop session to be able to launch a browser
// We need a desktop session to be able to launch the browser in the general case
return IsDesktopSession;
}

Expand Down
2 changes: 1 addition & 1 deletion src/shared/Core/Interop/MacOS/MacOSSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace GitCredentialManager.Interop.MacOS
{
public class MacOSSessionManager : PosixSessionManager
{
public MacOSSessionManager(IEnvironment env, IFileSystem fs) : base(env, fs)
public MacOSSessionManager(ITrace trace, IEnvironment env, IFileSystem fs) : base(trace, env, fs)
{
PlatformUtils.EnsureMacOS();
}
Expand Down
2 changes: 1 addition & 1 deletion src/shared/Core/Interop/Posix/PosixSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace GitCredentialManager.Interop.Posix
{
public abstract class PosixSessionManager : SessionManager
{
protected PosixSessionManager(IEnvironment env, IFileSystem fs) : base(env, fs)
protected PosixSessionManager(ITrace trace, IEnvironment env, IFileSystem fs) : base(trace, env, fs)
{
PlatformUtils.EnsurePosix();
}
Expand Down
2 changes: 1 addition & 1 deletion src/shared/Core/Interop/Windows/WindowsSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace GitCredentialManager.Interop.Windows
{
public class WindowsSessionManager : SessionManager
{
public WindowsSessionManager(IEnvironment env, IFileSystem fs) : base(env, fs)
public WindowsSessionManager(ITrace trace, IEnvironment env, IFileSystem fs) : base(trace, env, fs)
{
PlatformUtils.EnsureWindows();
}
Expand Down
Loading