Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend browser user authentication provide to handle Condition Access #361

Closed
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ public async Task SetupAsync(IUserManager userManager)
{
launchOptions.Channel = browserConfig.Channel;
}
else
{
if (browserConfig.Browser?.ToLower() == "chromium")
{
// Default to msedge browser
launchOptions.Channel = "msedge";
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reset the channel to input value from the user if it is specifically requested in the yaml

}

Browser = await browser.LaunchAsync(launchOptions);
_singleTestInstanceState.GetLogger().LogInformation("Browser setup finished");
Expand Down Expand Up @@ -160,6 +168,15 @@ public async Task SetupAsync(IUserManager userManager)
{
staticContext.Channel = browserConfig.Channel;
}
else
{
if (browserConfig.Browser?.ToLower() == "chromium")
{
// Default to msedge browser
launchOptions.Channel = "msedge";
}
}

BrowserContext = await browser.LaunchPersistentContextAsync(location, staticContext);
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace testengine.user.environment.tests
{
public class BrowserUserManagerModuleTests
{
private Mock<IBrowserContext> MockBrowserState;
private Mock<ITestInfraFunctions> MockTestInfraFunctions;
private Mock<ITestState> MockTestState;
private Mock<ISingleTestInstanceState> MockSingleTestInstanceState;
Expand All @@ -26,7 +25,6 @@ public class BrowserUserManagerModuleTests

public BrowserUserManagerModuleTests()
{
MockBrowserState = new Mock<IBrowserContext>(MockBehavior.Strict);
MockTestInfraFunctions = new Mock<ITestInfraFunctions>(MockBehavior.Strict);
MockTestState = new Mock<ITestState>(MockBehavior.Strict);
MockSingleTestInstanceState = new Mock<ISingleTestInstanceState>(MockBehavior.Strict);
Expand Down Expand Up @@ -59,9 +57,24 @@ public async Task LoginWithBrowserState(bool exists, bool isDirectoryCreated, st
userManager.GetFiles = (path) => files.Split(',');
userManager.Page = MockPage.Object;

MockSingleTestInstanceState.Setup(x => x.GetLogger()).Returns(MockLogger.Object);

MockTestState.Setup(x => x.GetTestSuiteDefinition()).Returns(new TestSuiteDefinition() { Persona = "User1" });
MockEnvironmentVariable.Setup(x => x.GetVariable("User1")).Returns("[email protected]");

MockBrowserContext.Setup(x => x.Pages).Returns(new List<IPage>() { MockPage.Object });
MockPage.Setup(x => x.Url).Returns("https://localhost/test");

MockLogger.Setup(x => x.Log(
It.IsAny<LogLevel>(),
It.IsAny<EventId>(),
It.IsAny<It.IsAnyType>(),
It.IsAny<Exception>(),
(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()));

// Act
await userManager.LoginAsUserAsync("*",
MockBrowserState.Object,
await userManager.LoginAsUserAsync("https://localhost/test",
MockBrowserContext.Object,
MockTestState.Object,
MockSingleTestInstanceState.Object,
MockEnvironmentVariable.Object,
Expand Down
75 changes: 70 additions & 5 deletions src/testengine.user.browser/BrowserUserManagerModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,15 @@
{
Context = context;

var logger = singleTestInstanceState.GetLogger();

if (!DirectoryExists(Location))
{
logger.LogInformation($"Creating {Location}");
CreateDirectory(Location);
}

if (GetFiles(Location).Count() == 0)
{
ValidatePage();
await Page.PauseAsync();
}
await LoginComplete(desiredUrl, testState, environmentVariable, logger);
}

private void ValidatePage()
Expand All @@ -66,5 +65,71 @@
Page = Context.Pages.First();
}
}

public async Task LoginComplete(string desiredUrl, ITestState testState, IEnvironmentVariable environmentVariable, ILogger logger)
{
var complete = false;

var persona = testState.GetTestSuiteDefinition().Persona;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null checks for the definition, persona and email

var personaEmail = environmentVariable.GetVariable(persona);

if (string.IsNullOrEmpty(personaEmail))
{
logger.LogInformation($"Missing user persona {persona} email. Prompting user");
Console.Write("Persona Email? ");
personaEmail = Console.ReadLine();
}

var started = DateTime.Now;

while (!complete)
{
foreach (var page in Context.Pages)
{
var host = new Uri(desiredUrl).Host;
if (page.Url.Contains($"https://{host}"))
{
logger.LogInformation($"Found destination url");
complete = true;
break;
}

if (page.Url.Contains("common/fido"))
{
logger.LogInformation($"Login required");
Console.WriteLine("Login required");
await page.PauseAsync();
}

if (page.Url.Contains("oauth2/authorize"))
{
if (await page.IsVisibleAsync($"[data-test-id=\"{personaEmail}\"]"))
{
logger.LogInformation($"Selecting {personaEmail}");

Check warning

Code scanning / CodeQL

Exposure of private information Medium test

Private data returned by
access to local variable personaEmail
is written to an external location.
Private data returned by
access to local variable personaEmail
is written to an external location.
Private data returned by
access to local variable personaEmail
is written to an external location.
Private data returned by
access to local variable personaEmail
is written to an external location.
await page.ClickAsync($"[data-test-id=\"{personaEmail}\"]");
}
}

if (page.Url.Contains("mcas.ms/aad_login"))
{
// TODO: Handle localized values
if (await page.GetByRole(AriaRole.Button, new() { Name = "Continue with current profile" }).IsVisibleAsync())
{
logger.LogInformation($"Continue with Microsoft Conditional Access");
await page.GetByRole(AriaRole.Button, new() { Name = "Continue with current profile" }).ClickAsync();
}
}
}

if (!complete)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to add a pause here to wait for user to attempt login

{
if (DateTime.Now.Subtract(started).TotalMinutes > 5)
{
throw new Exception("Unable to complete login");
}
Thread.Sleep(500);
}
}
}
}
}
Loading