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
15 changes: 15 additions & 0 deletions source/P4VFS.Console/P4VFS.Notes.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
Microsoft P4VFS Release Notes

Version [1.29.3.0]
* Perforce SSO login from URL now uses impersonated p4vfs login command and ShellExecute
instead of using cmd.exe. This fixes rare case lingering cmd.exe processes from failed
launch of the browser when there's no desktop session. This also prevents cmd.exe
with inherited handles, including TCP ports, from remaining open after service
is stopped and possibly causing failed port reopening after reinstall.
* Added option 'login -u <url>' for handling login challenge in web browser
* Added option 'login -t <seconds>' for timeout of login command before self
termination. This offers safety from possible lingering interactive prompts.
* Fixing placeholder file detection to ignore NTFS Zone and DLP streams for
file disk size calculation. This is primarily used in verification tests.
* Addition of ShellLoginTimeoutTest with simulation of Perforce server extension
similar to Helix Authentication Service for testing response to sso-auth-check
challenge from URL

Version [1.29.2.0]
* Replacing authentication for public driver codesign from Hardware Development
Center from deprecated client secret to x509 certificate. This is similar to
Expand Down
75 changes: 68 additions & 7 deletions source/P4VFS.Console/Source/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.P4VFS.Extensions;
using Microsoft.P4VFS.Extensions.Linq;
using Microsoft.P4VFS.Extensions.Utilities;
Expand Down Expand Up @@ -57,6 +58,8 @@ sync Synchronize the client with its view of the depot.
info Print out client/server information
set Modify current service settings temporarily for this login session.
resident Modify current resident status of local files.
hydrate Change file status to resident state (full downloaded size).
dehydrate Change file status to virtual state (zero downloaded size).
populate Perform sync as fast as possible using quiet, single flush
reconfig Modify the perforce configuration of local placeholder files.
monitor Launch and control the P4VFS monitor application.
Expand Down Expand Up @@ -207,10 +210,12 @@ p4vfs uninstall [-s -d]
{"login", @"
login Login to the perforce server and update the current ticket.

p4vfs login [-i -w] [password]
p4vfs login [-i -w] [-u <url>] [-t <seconds>] [password]

-i Display a modal dialog for password entry
-w Write the password to stdout after entering
-u Open a browser window with login challenge URL
-t Timeout waiting for login to complete
"},

{"test", @"
Expand Down Expand Up @@ -993,6 +998,9 @@ private static bool CommandLogin(string[] args)
{
bool interactive = false;
bool writePasswd = false;
int timeoutSeconds = 0;
string shellUrl = null;

int argIndex = 0;
for (; argIndex < args.Length; ++argIndex)
{
Expand All @@ -1004,12 +1012,52 @@ private static bool CommandLogin(string[] args)
{
writePasswd = true;
}
else if (String.Compare(args[argIndex], "-t") == 0 && argIndex+1 < args.Length)
{
if (Int32.TryParse(args[++argIndex], out timeoutSeconds) == false)
{
VirtualFileSystemLog.Error("Invalid login timeout specified: {0}", args[argIndex]);
return false;
}
}
else if (String.Compare(args[argIndex], "-u") == 0 && argIndex+1 < args.Length)
{
shellUrl = args[++argIndex];
if (Uri.IsWellFormedUriString(shellUrl, UriKind.Absolute) == false)
{
VirtualFileSystemLog.Error("Invalid shell URL specified: {0}", shellUrl);
return false;
}
}
else
{
break;
}
}

CancellationToken cancellationToken = CancellationToken.None;
if (timeoutSeconds > 0)
{
cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds)).Token;
}

if (String.IsNullOrEmpty(shellUrl) == false)
{
VirtualFileSystemLog.Info("Login from URL: {0}", shellUrl);
ProcessInfo.ExecuteResult executeResult = ProcessInfo.ExecuteWait(new ProcessInfo.ExecuteParams{
FileName = shellUrl,
UseShell = true,
LogOutput = false,
LogCommand = false,
CancellationToken = cancellationToken
});
if (executeResult.WasCanceled)
{
VirtualFileSystemLog.Info("Timeout waiting for shell login from URL");
}
return true;
}

DepotConfig p4Config = DepotInfo.DepotConfigFromPath(_P4Directory);

if (String.IsNullOrEmpty(_P4Port))
Expand Down Expand Up @@ -1064,13 +1112,15 @@ private static bool CommandLogin(string[] args)
{
if (interactive)
{
var thread = new System.Threading.Thread(new System.Threading.ThreadStart(() =>
Thread thread = new Thread(new ThreadStart(() =>
{
var dlg = new Microsoft.P4VFS.Extensions.Controls.LoginWindow();
Extensions.Controls.LoginWindow dlg = new Extensions.Controls.LoginWindow();
dlg.Port = _P4Port;
dlg.Client = _P4Client;
dlg.User = _P4User;
dlg.Passwd = _P4Passwd;
dlg.CancellationToken = cancellationToken;

if (dlg.ShowDialog() == true)
{
_P4Passwd = dlg.Passwd;
Expand All @@ -1081,14 +1131,25 @@ private static bool CommandLogin(string[] args)
}
}));

thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
else if (String.IsNullOrEmpty(_P4Passwd))
{
System.Console.Write("Enter Password: ");
_P4Passwd = ConsoleReader.ReadLine();
try
{
System.Console.Write("Enter Password: ");
_P4Passwd = ConsoleReader.ReadLine(cancellationToken);
}
catch (OperationCanceledException)
{}
}

if (cancellationToken.IsCancellationRequested)
{
VirtualFileSystemLog.Info("Timeout waiting for password to login");
return false;
}
}

Expand Down Expand Up @@ -1243,7 +1304,7 @@ private static bool PredicateLogRetry(Func<bool> expression, string message, int
return true;
}

System.Threading.Thread.Sleep(retryWait);
Thread.Sleep(retryWait);
}
while (DateTime.Now < endTime);

Expand Down
2 changes: 1 addition & 1 deletion source/P4VFS.Core/Include/FileOperations.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ namespace FileOperations {
CreateProcessImpersonated(
const WCHAR* commandLine,
const WCHAR* currentDirectory,
BOOL waitForExit,
FileCore::Process::ExecuteFlags::Enum flags,
FileCore::String* stdOutput = nullptr,
const FileCore::UserContext* context = nullptr
);
Expand Down
8 changes: 5 additions & 3 deletions source/P4VFS.Core/Source/DepotClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,9 @@ class DepotClientCommand : public ClientUser, IDepotClientCommand
if (url != nullptr && FileCore::StringInfo::IsNullOrEmpty(url->Text()) == false)
{
UserContext* context = m_Client->GetUserContext();
DepotString cmd = StringInfo::Format("cmd.exe /c start %s", url->Text());
FileOperations::CreateProcessImpersonated(CSTR_ATOW(cmd), nullptr, FALSE, nullptr, context);
WString cmd = StringInfo::Format(L"\"%s\\p4vfs.exe\" %s login -t 60 -u \"%s\"", FileInfo::FolderPath(FileInfo::ApplicationFilePath().c_str()).c_str(), CSTR_ATOW(m_Client->Config().ToCommandString()), CSTR_ATOW(url->Text()));
Process::ExecuteFlags::Enum flags = Process::ExecuteFlags::HideWindow;
FileOperations::CreateProcessImpersonated(cmd.c_str(), nullptr, flags, nullptr, context);
}
}

Expand Down Expand Up @@ -975,7 +976,8 @@ bool FDepotClient::RequestInteractivePassword(DepotString& passwd)
WString output;
UserContext* context = m_P4->m_FileContext ? m_P4->m_FileContext->m_UserContext : nullptr;
WString cmd = StringInfo::Format(L"\"%s\\p4vfs.exe\" %s login -i -w", FileInfo::FolderPath(FileInfo::ApplicationFilePath().c_str()).c_str(), CSTR_ATOW(m_P4->m_Config.ToCommandString()));
if (SUCCEEDED(FileOperations::CreateProcessImpersonated(cmd.c_str(), nullptr, TRUE, &output, context)))
Process::ExecuteFlags::Enum flags = Process::ExecuteFlags::WaitForExit | Process::ExecuteFlags::HideWindow;
if (SUCCEEDED(FileOperations::CreateProcessImpersonated(cmd.c_str(), nullptr, flags, &output, context)))
{
WStringArray lines = StringInfo::Split(output.c_str(), L"\n\r", StringInfo::SplitFlags::RemoveEmptyEntries);
for (const WString& line : lines)
Expand Down
26 changes: 23 additions & 3 deletions source/P4VFS.Core/Source/FileCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1443,14 +1443,34 @@ int64_t FileInfo::FileDiskSize(const wchar_t* filePath)
const STREAM_LAYOUT_ENTRY* streamEntry = reinterpret_cast<const STREAM_LAYOUT_ENTRY*>(streamEntryOrigin);
streamEntryOffset = streamEntry->NextStreamOffset;
if (streamEntry->AttributeFlags & FILE_ATTRIBUTE_SPARSE_FILE)
{
continue;
}

const WCHAR dataStreamName[] = L":$DATA";
const size_t dataStreamNameSize = (_countof(dataStreamName)-1)*sizeof(WCHAR);
if (streamEntry->StreamIdentifierLength == 0)
{
fileSize.QuadPart += streamEntry->AllocationSize.QuadPart;
else if (streamEntry->StreamIdentifierLength >= dataStreamNameSize && memcmp(dataStreamName, reinterpret_cast<const uint8_t*>(streamEntry->StreamIdentifier)+streamEntry->StreamIdentifierLength-dataStreamNameSize, dataStreamNameSize) == 0)
continue;
}

auto isMatchingStreamEntryName = [streamEntry](const WCHAR* dataName) -> bool
{
const size_t dataNameLength = wcslen(dataName);
const size_t streamNameLength = streamEntry->StreamIdentifierLength / sizeof(WCHAR);
return (streamNameLength >= dataNameLength && _wcsnicmp(dataName, reinterpret_cast<const WCHAR*>(streamEntry->StreamIdentifier) + streamNameLength - dataNameLength, dataNameLength) == 0);
};

if (isMatchingStreamEntryName(L":SEC.ENDPOINTDLP:$DATA") ||
isMatchingStreamEntryName(L":ZONE.IDENTIFIER:$DATA"))
{
continue;
}

if (isMatchingStreamEntryName(L":$DATA"))
{
fileSize.QuadPart += streamEntry->AllocationSize.QuadPart;
continue;
}
}
break;
}
Expand Down
7 changes: 1 addition & 6 deletions source/P4VFS.Core/Source/FileOperations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1686,7 +1686,7 @@ HRESULT
CreateProcessImpersonated(
const WCHAR* commandLine,
const WCHAR* currentDirectory,
BOOL waitForExit,
FileCore::Process::ExecuteFlags::Enum flags,
FileCore::String* stdOutput,
const FileCore::UserContext* context
)
Expand All @@ -1702,11 +1702,6 @@ CreateProcessImpersonated(
return HRESULT_FROM_WIN32(ERROR_INVALID_TOKEN);
}

FileCore::Process::ExecuteFlags::Enum flags = FileCore::Process::ExecuteFlags::HideWindow;
if (waitForExit)
{
flags |= FileCore::Process::ExecuteFlags::WaitForExit;
}
if (stdOutput != nullptr)
{
flags |= FileCore::Process::ExecuteFlags::StdOut;
Expand Down
14 changes: 13 additions & 1 deletion source/P4VFS.CoreInterop/Include/CoreInterop.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,18 @@ public ref class LogSystem abstract sealed
);
};

[System::FlagsAttribute]
public enum class ProcessExecuteFlags : System::Int32
{
None = FileCore::Process::ExecuteFlags::None,
WaitForExit = FileCore::Process::ExecuteFlags::WaitForExit,
HideWindow = FileCore::Process::ExecuteFlags::HideWindow,
StdOut = FileCore::Process::ExecuteFlags::StdOut,
KeepOpen = FileCore::Process::ExecuteFlags::KeepOpen,
Unelevated = FileCore::Process::ExecuteFlags::Unelevated,
Default = FileCore::Process::ExecuteFlags::Default,
};

public ref class NativeMethods abstract sealed
{
public:
Expand Down Expand Up @@ -219,7 +231,7 @@ public ref class NativeMethods abstract sealed
CreateProcessImpersonated(
System::String^ commandLine,
System::String^ currentDirectory,
System::Boolean waitForExit,
ProcessExecuteFlags flags,
System::Text::StringBuilder^ stdOutput,
UserContext^ context
);
Expand Down
4 changes: 2 additions & 2 deletions source/P4VFS.CoreInterop/Source/CoreInterop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ System::Boolean
NativeMethods::CreateProcessImpersonated(
System::String^ commandLine,
System::String^ currentDirectory,
System::Boolean waitForExit,
ProcessExecuteFlags flags,
System::Text::StringBuilder^ stdOutput,
UserContext^ context
)
Expand All @@ -334,7 +334,7 @@ NativeMethods::CreateProcessImpersonated(
HRESULT status = FileOperations::CreateProcessImpersonated(
marshal_as_wstring_c_str(commandLine),
marshal_as_wstring_c_str(currentDirectory),
waitForExit,
static_cast<FileCore::Process::ExecuteFlags::Enum>(flags),
stdOutput != nullptr ? &stdOutputResult : nullptr,
marshal_as_user_context(context)
);
Expand Down
36 changes: 25 additions & 11 deletions source/P4VFS.Extensions/Source/Controls/LoginWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Threading.Tasks;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Microsoft.P4VFS.Extensions.Controls
{
public partial class LoginWindow : Window
{
private CancellationToken? m_CancellationToken;
private DispatcherTimer m_UpdateTimer;

public LoginWindow()
{
InitializeComponent();

m_UpdateTimer = new DispatcherTimer();
m_UpdateTimer.Interval = TimeSpan.FromSeconds(1.0/30.0);
m_UpdateTimer.Tick += OnTickUpdateTimer;
m_UpdateTimer.Start();
}

public string Port
public string Port
{
get { return m_Port.Text; }
set { m_Port.Text = value; }
Expand All @@ -48,6 +48,20 @@ public string Passwd
set { m_Passwd.Password = value; }
}

public CancellationToken? CancellationToken
{
get { return m_CancellationToken; }
set { m_CancellationToken = value; }
}

private void OnTickUpdateTimer(object sender, EventArgs e)
{
if (m_CancellationToken?.IsCancellationRequested == true)
{
this.DialogResult = false;
}
}

private void OnClickButtonCancel(object sender, RoutedEventArgs e)
{
this.DialogResult = false;
Expand Down
Loading
Loading