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

chore(playwrighttesting): removed fallback authentication logic #46667

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -199,6 +199,8 @@ internal class Constants
internal static readonly string s_invalid_mpt_pat_error = "The Access Token provided in the environment variable is invalid.";
internal static readonly string s_expired_mpt_pat_error = "The Access Token you are using is expired. Create a new token.";
internal static readonly string s_invalid_os_error = "Invalid operating system, supported values are 'linux' and 'windows'.";
internal static readonly string s_workspace_mismatch_error = "The provided access token does not match the specified workspace URL. Please verify that both values are correct.";
internal static readonly string s_invalid_service_endpoint_error_message = "The service endpoint provided is invalid. Please verify the endpoint URL and try again.";

internal static readonly string s_playwright_service_disable_scalable_execution_environment_variable = "PLAYWRIGHT_SERVICE_DISABLE_SCALABLE_EXECUTION";
internal static readonly string s_playwright_service_reporting_url_environment_variable = "PLAYWRIGHT_SERVICE_REPORTING_URL";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public EntraLifecycle(TokenCredential? tokenCredential = null, JsonWebTokenHandl
SetEntraIdAccessTokenFromEnvironment();
}

internal async Task<bool> FetchEntraIdAccessTokenAsync(CancellationToken cancellationToken = default)
internal async Task FetchEntraIdAccessTokenAsync(CancellationToken cancellationToken = default)
{
try
{
Expand All @@ -33,12 +33,12 @@ internal async Task<bool> FetchEntraIdAccessTokenAsync(CancellationToken cancell
_entraIdAccessToken = accessToken.Token;
_entraIdAccessTokenExpiry = accessToken.ExpiresOn.ToUnixTimeSeconds();
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, _entraIdAccessToken);
return true;
return;
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return false;
throw new Exception(Constants.s_no_auth_error);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.IdentityModel.JsonWebTokens;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
Expand Down Expand Up @@ -107,9 +108,9 @@ internal PlaywrightService(OSPlatform? os = null, string? runId = null, string?
// fetch Entra id access token if required
// 1. Entra id access token has been fetched once via global functions
// 2. Not close to expiry
if (!string.IsNullOrEmpty(_entraLifecycle!._entraIdAccessToken) && _entraLifecycle!.DoesEntraIdAccessTokenRequireRotation())
if (!string.IsNullOrEmpty(_entraLifecycle!._entraIdAccessToken) && _entraLifecycle!.DoesEntraIdAccessTokenRequireRotation() && ServiceAuth == ServiceAuthType.EntraId)
kashish2508 marked this conversation as resolved.
Show resolved Hide resolved
{
_ = await _entraLifecycle.FetchEntraIdAccessTokenAsync(cancellationToken).ConfigureAwait(false);
await _entraLifecycle.FetchEntraIdAccessTokenAsync(cancellationToken).ConfigureAwait(false);
}
if (string.IsNullOrEmpty(GetAuthToken()))
{
Expand Down Expand Up @@ -146,24 +147,15 @@ public async Task InitializeAsync(CancellationToken cancellationToken = default)
Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceUri, null);
}
// If default auth mechanism is Access token and token is available in the environment variable, no need to setup rotation handler
if (ServiceAuth == ServiceAuthType.AccessToken && !string.IsNullOrEmpty(GetAuthToken()))
if (ServiceAuth == ServiceAuthType.AccessToken)
{
ValidateMptPAT();
return;
}
var operationStatus = await _entraLifecycle!.FetchEntraIdAccessTokenAsync(cancellationToken).ConfigureAwait(false);
if (!operationStatus)
{
if (!string.IsNullOrEmpty(GetAuthToken()))
{
ValidateMptPAT(); // throws exception if token is invalid
}
return; // no need to setup rotation handler. If token is not available, it will fallback to local browser launch
await _entraLifecycle!.FetchEntraIdAccessTokenAsync(cancellationToken).ConfigureAwait(false);
RotationTimer = new Timer(RotationHandlerAsync, null, TimeSpan.FromMinutes(Constants.s_entra_access_token_rotation_interval_period_in_minutes), TimeSpan.FromMinutes(Constants.s_entra_access_token_rotation_interval_period_in_minutes));
}

RotationTimer = new Timer(RotationHandlerAsync, null, TimeSpan.FromMinutes(Constants.s_entra_access_token_rotation_interval_period_in_minutes), TimeSpan.FromMinutes(Constants.s_entra_access_token_rotation_interval_period_in_minutes));
}

/// <summary>
/// Cleans up the resources used to setup entra id authentication.
/// </summary>
Expand Down Expand Up @@ -254,14 +246,23 @@ private void ValidateMptPAT()
try
{
string authToken = GetAuthToken()!;
if (string.IsNullOrEmpty(authToken))
throw new Exception(Constants.s_no_auth_error);
JsonWebToken jsonWebToken = _jsonWebTokenHandler!.ReadJsonWebToken(authToken) ?? throw new Exception(Constants.s_invalid_mpt_pat_error);
var tokenaWorkspaceId = jsonWebToken.Claims.FirstOrDefault(c => c.Type == "aid")?.Value;
kashish2508 marked this conversation as resolved.
Show resolved Hide resolved
Match match = Regex.Match(ServiceEndpoint, @"wss://(?<region>[\w-]+)\.api\.(?<domain>playwright(?:-test|-int)?\.io|playwright\.microsoft\.com)/accounts/(?<workspaceId>[\w-]+)/");
if (!match.Success)
throw new Exception(Constants.s_invalid_service_endpoint_error_message);
var serviceEndpointworkspaceId = match.Groups["workspaceId"].Value;
kashish2508 marked this conversation as resolved.
Show resolved Hide resolved
if (tokenaWorkspaceId != serviceEndpointworkspaceId)
throw new Exception(Constants.s_workspace_mismatch_error);
var expiry = (long)(jsonWebToken.ValidTo - new DateTime(1970, 1, 1)).TotalSeconds;
if (expiry <= DateTimeOffset.UtcNow.ToUnixTimeSeconds())
throw new Exception(Constants.s_expired_mpt_pat_error);
}
catch (Exception)
kashish2508 marked this conversation as resolved.
Show resolved Hide resolved
{
throw new Exception(Constants.s_invalid_mpt_pat_error);
throw;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Runtime.InteropServices;
using Azure.Core;
using Azure.Identity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public async Task FetchEntraIdAccessTokenAsync_WhenTokenIsFetched_SetsTokenAndEx
}
kashish2508 marked this conversation as resolved.
Show resolved Hide resolved

[Test]
public async Task FetchEntraIdAccessTokenAsync_WhenTokenIsFetched_ReturnsTrue()
public async Task FetchEntraIdAccessTokenAsync_WhenTokenIsFetched_ReturnVoid()
{
var defaultAzureCredentialMock = new Mock<DefaultAzureCredential>();
var token = "valid_token";
Expand All @@ -141,20 +141,23 @@ public async Task FetchEntraIdAccessTokenAsync_WhenTokenIsFetched_ReturnsTrue()
.Setup(x => x.GetTokenAsync(It.IsAny<TokenRequestContext>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new AccessToken(token, expiry));
EntraLifecycle entraLifecycle = new(defaultAzureCredentialMock.Object);
Assert.That(await entraLifecycle.FetchEntraIdAccessTokenAsync(), Is.True);
await entraLifecycle.FetchEntraIdAccessTokenAsync();

Environment.SetEnvironmentVariable(ServiceEnvironmentVariable.PlaywrightServiceAccessToken, null);
}

[Test]
public async Task FetchEntraIdAccessTokenAsync_WhenThrowsError_ReturnsFalse()
public void FetchEntraIdAccessTokenAsync_WhenThrowsError()
{
var defaultAzureCredentialMock = new Mock<DefaultAzureCredential>();
defaultAzureCredentialMock
.Setup(x => x.GetTokenAsync(It.IsAny<TokenRequestContext>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new Exception("sample exception"));
EntraLifecycle entraLifecycle = new(defaultAzureCredentialMock.Object);
Assert.That(await entraLifecycle.FetchEntraIdAccessTokenAsync(), Is.False);
Exception? ex = Assert.ThrowsAsync<Exception>(async () =>
await entraLifecycle.FetchEntraIdAccessTokenAsync());

Assert.That(ex!.Message, Is.EqualTo(Constants.s_no_auth_error));
}

[Test]
Expand Down
Loading