mirror of
https://github.com/actions/runner.git
synced 2025-12-10 20:36:49 +00:00
rm aad
This commit is contained in:
@@ -158,7 +158,6 @@ namespace GitHub.Runner.Common
|
||||
|
||||
public static class Configuration
|
||||
{
|
||||
public static readonly string AAD = "AAD";
|
||||
public static readonly string OAuthAccessToken = "OAuthAccessToken";
|
||||
public static readonly string OAuth = "OAuth";
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
Trace.Info(nameof(GetCredentialProvider));
|
||||
|
||||
var credentialManager = HostContext.GetService<ICredentialManager>();
|
||||
string authType = command.GetAuth(defaultValue: Constants.Configuration.AAD);
|
||||
string authType = command.GetAuth(defaultValue: Constants.Configuration.OAuthAccessToken);
|
||||
|
||||
// Create the credential.
|
||||
Trace.Info("Creating credential for auth: {0}", authType);
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
{
|
||||
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)},
|
||||
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
|
||||
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
|
||||
};
|
||||
|
||||
@@ -37,125 +37,6 @@ namespace GitHub.Runner.Listener.Configuration
|
||||
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
|
||||
}
|
||||
|
||||
public sealed class AadDeviceCodeAccessToken : CredentialProvider
|
||||
{
|
||||
private string _azureDevOpsClientId = "97877f11-0fc6-4aee-b1ff-febb0519dd00";
|
||||
|
||||
public override Boolean RequireInteractive => true;
|
||||
|
||||
public AadDeviceCodeAccessToken() : base(Constants.Configuration.AAD) { }
|
||||
|
||||
public override VssCredentials GetVssCredentials(IHostContext context)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
|
||||
trace.Info(nameof(GetVssCredentials));
|
||||
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
|
||||
|
||||
CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Url, out string serverUrl);
|
||||
ArgUtil.NotNullOrEmpty(serverUrl, nameof(serverUrl));
|
||||
|
||||
var tenantAuthorityUrl = GetTenantAuthorityUrl(context, serverUrl);
|
||||
if (tenantAuthorityUrl == null)
|
||||
{
|
||||
throw new NotSupportedException($"'{serverUrl}' is not backed by Azure Active Directory.");
|
||||
}
|
||||
|
||||
LoggerCallbackHandler.LogCallback = ((LogLevel level, string message, bool containsPii) =>
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case LogLevel.Information:
|
||||
trace.Info(message);
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
trace.Error(message);
|
||||
break;
|
||||
case LogLevel.Warning:
|
||||
trace.Warning(message);
|
||||
break;
|
||||
default:
|
||||
trace.Verbose(message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
LoggerCallbackHandler.UseDefaultLogging = false;
|
||||
AuthenticationContext ctx = new AuthenticationContext(tenantAuthorityUrl.AbsoluteUri);
|
||||
var queryParameters = $"redirect_uri={Uri.EscapeDataString(new Uri(serverUrl).GetLeftPart(UriPartial.Authority))}";
|
||||
DeviceCodeResult codeResult = ctx.AcquireDeviceCodeAsync("https://management.core.windows.net/", _azureDevOpsClientId, queryParameters).GetAwaiter().GetResult();
|
||||
|
||||
var term = context.GetService<ITerminal>();
|
||||
term.WriteLine($"Please finish AAD device code flow in browser ({codeResult.VerificationUrl}), user code: {codeResult.UserCode}");
|
||||
if (string.Equals(CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser], bool.TrueString, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
#if OS_WINDOWS
|
||||
Process.Start(new ProcessStartInfo() { FileName = codeResult.VerificationUrl, UseShellExecute = true });
|
||||
#elif OS_LINUX
|
||||
Process.Start(new ProcessStartInfo() { FileName = "xdg-open", Arguments = codeResult.VerificationUrl });
|
||||
#else
|
||||
Process.Start(new ProcessStartInfo() { FileName = "open", Arguments = codeResult.VerificationUrl });
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// not able to open browser, ex: xdg-open/open is not installed.
|
||||
trace.Error(ex);
|
||||
term.WriteLine($"Fail to open browser. {codeResult.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
AuthenticationResult authResult = ctx.AcquireTokenByDeviceCodeAsync(codeResult).GetAwaiter().GetResult();
|
||||
ArgUtil.NotNull(authResult, nameof(authResult));
|
||||
trace.Info($"receive AAD auth result with {authResult.AccessTokenType} token");
|
||||
|
||||
var aadCred = new VssAadCredential(new VssAadToken(authResult));
|
||||
VssCredentials creds = new VssCredentials(null, aadCred, CredentialPromptType.DoNotPrompt);
|
||||
trace.Info("cred created");
|
||||
|
||||
return creds;
|
||||
}
|
||||
|
||||
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
|
||||
{
|
||||
ArgUtil.NotNull(context, nameof(context));
|
||||
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
|
||||
trace.Info(nameof(EnsureCredential));
|
||||
ArgUtil.NotNull(command, nameof(command));
|
||||
CredentialData.Data[Constants.Runner.CommandLine.Args.Url] = serverUrl;
|
||||
CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser] = command.GetAutoLaunchBrowser().ToString();
|
||||
}
|
||||
|
||||
private Uri GetTenantAuthorityUrl(IHostContext context, string serverUrl)
|
||||
{
|
||||
using (var client = new HttpClient(context.CreateHttpClientHandler()))
|
||||
{
|
||||
client.DefaultRequestHeaders.Accept.Clear();
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
|
||||
client.DefaultRequestHeaders.UserAgent.Clear();
|
||||
client.DefaultRequestHeaders.UserAgent.AddRange(VssClientHttpRequestSettings.Default.UserAgent);
|
||||
var requestMessage = new HttpRequestMessage(HttpMethod.Head, $"{serverUrl.Trim('/')}/_apis/connectiondata");
|
||||
var response = client.SendAsync(requestMessage).GetAwaiter().GetResult();
|
||||
|
||||
// Get the tenant from the Login URL, MSA backed accounts will not return `Bearer` www-authenticate header.
|
||||
var bearerResult = response.Headers.WwwAuthenticate.Where(p => p.Scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
|
||||
if (bearerResult != null && bearerResult.Parameter.StartsWith("authorization_uri=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var authorizationUri = bearerResult.Parameter.Substring("authorization_uri=".Length);
|
||||
if (Uri.TryCreate(authorizationUri, UriKind.Absolute, out Uri aadTenantUrl))
|
||||
{
|
||||
return aadTenantUrl;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class OAuthAccessTokenCredential : CredentialProvider
|
||||
{
|
||||
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
|
||||
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.19.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
internal static class CookieUtility
|
||||
{
|
||||
public static readonly String AcsMetadataRetrievalExceptionText = "Unable to retrieve ACS Metadata from '{0}'";
|
||||
public static readonly String FedAuthCookieName = "FedAuth";
|
||||
public static readonly String WindowsLiveSignOutUrl = "https://login.live.com/uilogout.srf";
|
||||
public static readonly Uri WindowsLiveCookieDomain = new Uri("https://login.live.com/");
|
||||
|
||||
public static CookieCollection GetFederatedCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
|
||||
Cookie cookie = GetCookieEx(cookieDomainAndPath, FedAuthCookieName).FirstOrDefault();
|
||||
|
||||
if (cookie != null)
|
||||
{
|
||||
result = new CookieCollection();
|
||||
result.Add(cookie);
|
||||
|
||||
for (Int32 x = 1; x < 50; x++)
|
||||
{
|
||||
String cookieName = FedAuthCookieName + x;
|
||||
cookie = GetCookieEx(cookieDomainAndPath, cookieName).FirstOrDefault();
|
||||
|
||||
if (cookie != null)
|
||||
{
|
||||
result.Add(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CookieCollection GetFederatedCookies(String[] token)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
|
||||
if (token != null && token.Length > 0 && token[0] != null)
|
||||
{
|
||||
result = new CookieCollection();
|
||||
result.Add(new Cookie(FedAuthCookieName, token[0]));
|
||||
|
||||
for (Int32 x = 1; x < token.Length; x++)
|
||||
{
|
||||
String cookieName = FedAuthCookieName + x;
|
||||
|
||||
if (token[x] != null)
|
||||
{
|
||||
Cookie cookie = new Cookie(cookieName, token[x]);
|
||||
cookie.HttpOnly = true;
|
||||
result.Add(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CookieCollection GetFederatedCookies(IHttpResponse webResponse)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
IEnumerable<String> cookies = null;
|
||||
|
||||
if (webResponse.Headers.TryGetValues("Set-Cookie", out cookies))
|
||||
{
|
||||
foreach (String cookie in cookies)
|
||||
{
|
||||
if (cookie != null && cookie.StartsWith(CookieUtility.FedAuthCookieName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Only take the security token field of the cookie, and discard the rest
|
||||
String fedAuthToken = cookie.Split(';').FirstOrDefault();
|
||||
Int32 index = fedAuthToken.IndexOf('=');
|
||||
|
||||
if (index > 0 && index < fedAuthToken.Length - 1)
|
||||
{
|
||||
String name = fedAuthToken.Substring(0, index);
|
||||
String value = fedAuthToken.Substring(index + 1);
|
||||
|
||||
result = result ?? new CookieCollection();
|
||||
result.Add(new Cookie(name, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CookieCollection GetAllCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection result = null;
|
||||
List<Cookie> cookies = GetCookieEx(cookieDomainAndPath, null);
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
result = new CookieCollection();
|
||||
}
|
||||
|
||||
result.Add(cookie);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void DeleteFederatedCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection cookies = GetFederatedCookies(cookieDomainAndPath);
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteWindowsLiveCookies()
|
||||
{
|
||||
DeleteAllCookies(WindowsLiveCookieDomain);
|
||||
}
|
||||
|
||||
public static void DeleteAllCookies(Uri cookieDomainAndPath)
|
||||
{
|
||||
CookieCollection cookies = GetAllCookies(cookieDomainAndPath);
|
||||
|
||||
if (cookies != null)
|
||||
{
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public const UInt32 INTERNET_COOKIE_HTTPONLY = 0x00002000;
|
||||
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
static extern bool InternetGetCookieEx(
|
||||
String url, String cookieName, StringBuilder cookieData, ref Int32 size, UInt32 flags, IntPtr reserved);
|
||||
|
||||
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
static extern bool InternetSetCookieEx(
|
||||
String url, String cookieName, String cookieData, UInt32 flags, IntPtr reserved);
|
||||
|
||||
public static Boolean DeleteCookieEx(Uri cookiePath, String cookieName)
|
||||
{
|
||||
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
|
||||
|
||||
String path = cookiePath.ToString();
|
||||
if (!path.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
DateTime expiration = DateTime.UtcNow.AddYears(-1);
|
||||
String cookieData = String.Format(CultureInfo.InvariantCulture, "{0}=0;expires={1};path=/;domain={2};httponly", cookieName, expiration.ToString("R"), cookiePath.Host);
|
||||
|
||||
return InternetSetCookieEx(path, null, cookieData, flags, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public static Boolean SetCookiesEx(
|
||||
Uri cookiePath,
|
||||
CookieCollection cookies)
|
||||
{
|
||||
String path = cookiePath.ToString();
|
||||
if (!path.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
Boolean successful = true;
|
||||
foreach (Cookie cookie in cookies)
|
||||
{
|
||||
// This means it doesn't expire
|
||||
if (cookie.Expires.Year == 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String cookieData = String.Format(CultureInfo.InvariantCulture,
|
||||
"{0}; path={1}; domain={2}; expires={3}; httponly",
|
||||
cookie.Value,
|
||||
cookie.Path,
|
||||
cookie.Domain,
|
||||
cookie.Expires.ToString("ddd, dd-MMM-yyyy HH:mm:ss 'GMT'"));
|
||||
|
||||
successful &= InternetSetCookieEx(path, cookie.Name, cookieData, INTERNET_COOKIE_HTTPONLY, IntPtr.Zero);
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
|
||||
public static List<Cookie> GetCookieEx(Uri cookiePath, String cookieName)
|
||||
{
|
||||
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
|
||||
|
||||
List<Cookie> cookies = new List<Cookie>();
|
||||
Int32 size = 256;
|
||||
StringBuilder cookieData = new StringBuilder(size);
|
||||
String path = cookiePath.ToString();
|
||||
if (!path.EndsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
path = path + "/";
|
||||
}
|
||||
|
||||
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
|
||||
{
|
||||
if (size < 0)
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
|
||||
cookieData = new StringBuilder(size);
|
||||
|
||||
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
|
||||
{
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieData.Length > 0)
|
||||
{
|
||||
String[] cookieSections = cookieData.ToString().Split(new char[] { ';' });
|
||||
|
||||
foreach (String cookieSection in cookieSections)
|
||||
{
|
||||
String[] cookieParts = cookieSection.Split(new char[] { '=' }, 2);
|
||||
|
||||
if (cookieParts.Length == 2)
|
||||
{
|
||||
Cookie cookie = new Cookie();
|
||||
cookie.Name = cookieParts[0].TrimStart();
|
||||
cookie.Value = cookieParts[1];
|
||||
cookie.HttpOnly = true;
|
||||
cookies.Add(cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Security;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Currently it is impossible to get whether prompting is allowed from the credential itself without reproducing the logic
|
||||
/// used by VssClientCredentials. Since this is a stop gap solution to get Windows integrated authentication to work against
|
||||
/// AAD via ADFS for now this class will only support that one, non-interactive flow. We need to assess how much we want to
|
||||
/// invest in this legacy stack rather than recommending people move to the VssConnect API for future authentication needs.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssAadCredential : FederatedCredential
|
||||
{
|
||||
private string username;
|
||||
private SecureString password;
|
||||
|
||||
public VssAadCredential()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
public VssAadCredential(VssAadToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public VssAadCredential(string username)
|
||||
: base(null)
|
||||
{
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public VssAadCredential(string username, string password)
|
||||
: base(null)
|
||||
{
|
||||
this.username = username;
|
||||
|
||||
if (password != null)
|
||||
{
|
||||
this.password = new SecureString();
|
||||
|
||||
foreach (char character in password)
|
||||
{
|
||||
this.password.AppendChar(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VssAadCredential(string username, SecureString password)
|
||||
: base(null)
|
||||
{
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Aad;
|
||||
}
|
||||
}
|
||||
|
||||
internal string Username
|
||||
{
|
||||
get
|
||||
{
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
||||
internal SecureString Password => password;
|
||||
|
||||
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
bool isNonAuthenticationChallenge = false;
|
||||
return VssFederatedCredential.IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? false;
|
||||
}
|
||||
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
if (response == null && base.InitialToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new VssAadTokenProvider(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using GitHub.Services.WebApi;
|
||||
using GitHub.Services.WebApi.Internal;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
internal static class VssAadSettings
|
||||
{
|
||||
public const string DefaultAadInstance = "https://login.microsoftonline.com/";
|
||||
|
||||
public const string CommonTenant = "common";
|
||||
|
||||
// VSTS service principal.
|
||||
public const string Resource = "499b84ac-1321-427f-aa17-267ca6975798";
|
||||
|
||||
// Visual Studio IDE client ID originally provisioned by Azure Tools.
|
||||
public const string Client = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
|
||||
|
||||
// AAD Production Application tenant.
|
||||
private const string ApplicationTenantId = "f8cdef31-a31e-4b4a-93e4-5f571e91255a";
|
||||
|
||||
#if !NETSTANDARD
|
||||
public static Uri NativeClientRedirectUri
|
||||
{
|
||||
get
|
||||
{
|
||||
Uri nativeClientRedirect = null;
|
||||
|
||||
try
|
||||
{
|
||||
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientRedirect);
|
||||
if (!string.IsNullOrEmpty(nativeRedirect))
|
||||
{
|
||||
Uri.TryCreate(nativeRedirect, UriKind.RelativeOrAbsolute, out nativeClientRedirect);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.WriteLine(string.Format("NativeClientRedirectUri: {0}", e));
|
||||
}
|
||||
|
||||
return nativeClientRedirect ?? new Uri("urn:ietf:wg:oauth:2.0:oob");
|
||||
}
|
||||
}
|
||||
|
||||
public static string ClientId
|
||||
{
|
||||
get
|
||||
{
|
||||
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientIdentifier);
|
||||
return nativeRedirect ?? VssAadSettings.Client;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public static string AadInstance
|
||||
{
|
||||
get
|
||||
{
|
||||
#if !NETSTANDARD
|
||||
string aadInstance = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadInstance);
|
||||
#else
|
||||
string aadInstance = null;
|
||||
#endif
|
||||
|
||||
if (string.IsNullOrWhiteSpace(aadInstance))
|
||||
{
|
||||
aadInstance = DefaultAadInstance;
|
||||
}
|
||||
else if (!aadInstance.EndsWith("/"))
|
||||
{
|
||||
aadInstance = aadInstance + "/";
|
||||
}
|
||||
|
||||
return aadInstance;
|
||||
}
|
||||
}
|
||||
|
||||
#if !NETSTANDARD
|
||||
/// <summary>
|
||||
/// Application tenant either from a registry override or a constant
|
||||
/// </summary>
|
||||
public static string ApplicationTenant =>
|
||||
VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadApplicationTenant)
|
||||
?? VssAadSettings.ApplicationTenantId;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
[Serializable]
|
||||
public class VssAadToken : IssuedToken
|
||||
{
|
||||
private string accessToken;
|
||||
private string accessTokenType;
|
||||
|
||||
private AuthenticationContext authenticationContext;
|
||||
private UserCredential userCredential;
|
||||
private VssAadTokenOptions options;
|
||||
|
||||
public VssAadToken(AuthenticationResult authentication)
|
||||
{
|
||||
// Prevent any attempt to store this token.
|
||||
this.FromStorage = true;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(authentication.AccessToken))
|
||||
{
|
||||
this.Authenticated();
|
||||
}
|
||||
|
||||
this.accessToken = authentication.AccessToken;
|
||||
this.accessTokenType = authentication.AccessTokenType;
|
||||
}
|
||||
|
||||
public VssAadToken(
|
||||
string accessTokenType,
|
||||
string accessToken)
|
||||
{
|
||||
// Prevent any attempt to store this token.
|
||||
this.FromStorage = true;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(accessToken) && !string.IsNullOrWhiteSpace(accessTokenType))
|
||||
{
|
||||
this.Authenticated();
|
||||
}
|
||||
|
||||
this.accessToken = accessToken;
|
||||
this.accessTokenType = accessTokenType;
|
||||
}
|
||||
|
||||
public VssAadToken(
|
||||
AuthenticationContext authenticationContext,
|
||||
UserCredential userCredential = null,
|
||||
VssAadTokenOptions options = VssAadTokenOptions.None)
|
||||
{
|
||||
// Prevent any attempt to store this token.
|
||||
this.FromStorage = true;
|
||||
|
||||
this.authenticationContext = authenticationContext;
|
||||
this.userCredential = userCredential;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Aad;
|
||||
}
|
||||
}
|
||||
|
||||
public AuthenticationResult AcquireToken()
|
||||
{
|
||||
if (this.authenticationContext == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
AuthenticationResult authenticationResult = null;
|
||||
|
||||
for (int index = 0; index < 3; index++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (this.userCredential == null && !options.HasFlag(VssAadTokenOptions.AllowDialog))
|
||||
{
|
||||
authenticationResult = authenticationContext.AcquireTokenSilentAsync(VssAadSettings.Resource, VssAadSettings.Client).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
authenticationResult = authenticationContext.AcquireTokenAsync(VssAadSettings.Resource, VssAadSettings.Client, this.userCredential).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
if (authenticationResult != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception x)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("Failed to get ADFS token: " + x.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return authenticationResult;
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
AuthenticationResult authenticationResult = AcquireToken();
|
||||
if (authenticationResult != null)
|
||||
{
|
||||
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{authenticationResult.AccessTokenType} {authenticationResult.AccessToken}");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(this.accessTokenType) && !string.IsNullOrEmpty(this.accessToken))
|
||||
{
|
||||
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{this.accessTokenType} {this.accessToken}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum VssAadTokenOptions
|
||||
{
|
||||
None = 0,
|
||||
AllowDialog = 1
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
internal sealed class VssAadTokenProvider : IssuedTokenProvider
|
||||
{
|
||||
public VssAadTokenProvider(VssAadCredential credential)
|
||||
: base(credential, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private VssAadToken GetVssAadToken()
|
||||
{
|
||||
AuthenticationContext authenticationContext = new AuthenticationContext(string.Concat(VssAadSettings.AadInstance, VssAadSettings.CommonTenant));
|
||||
UserCredential userCredential = null;
|
||||
|
||||
VssAadCredential credential = this.Credential as VssAadCredential;
|
||||
|
||||
if (credential?.Username != null)
|
||||
{
|
||||
#if NETSTANDARD
|
||||
// UserPasswordCredential does not currently exist for ADAL 3.13.5 for any non-desktop build.
|
||||
userCredential = new UserCredential(credential.Username);
|
||||
#else
|
||||
if (credential.Password != null)
|
||||
{
|
||||
userCredential = new UserPasswordCredential(credential.Username, credential.Password);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
userCredential = new UserCredential(credential.Username);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
userCredential = new UserCredential();
|
||||
}
|
||||
|
||||
return new VssAadToken(authenticationContext, userCredential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Temporary implementation since we don't have a good configuration story here at the moment.
|
||||
/// </summary>
|
||||
protected override Task<IssuedToken> OnGetTokenAsync(IssuedToken failedToken, CancellationToken cancellationToken)
|
||||
{
|
||||
// If we have already tried to authenticate with an AAD token retrieved from Windows integrated authentication and it is not working, clear out state.
|
||||
if (failedToken != null && failedToken.CredentialType == VssCredentialsType.Aad && failedToken.IsAuthenticated)
|
||||
{
|
||||
this.CurrentToken = null;
|
||||
return Task.FromResult<IssuedToken>(null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Task.FromResult<IssuedToken>(GetVssAadToken());
|
||||
}
|
||||
catch
|
||||
{ }
|
||||
|
||||
return Task.FromResult<IssuedToken>(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using GitHub.Services.Common;
|
||||
using GitHub.Services.Common.Internal;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides federated authentication with a hosted <c>VssConnection</c> instance using cookies.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssFederatedCredential : FederatedCredential
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedCredential</c> instance.
|
||||
/// </summary>
|
||||
public VssFederatedCredential()
|
||||
: this(true)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedCredential</c> instance.
|
||||
/// </summary>
|
||||
public VssFederatedCredential(Boolean useCache)
|
||||
: this(useCache, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedCredential</c> instance.
|
||||
/// </summary>
|
||||
/// <param name="initialToken">The initial token if available</param>
|
||||
public VssFederatedCredential(VssFederatedToken initialToken)
|
||||
: this(false, initialToken)
|
||||
{
|
||||
}
|
||||
|
||||
public VssFederatedCredential(
|
||||
Boolean useCache,
|
||||
VssFederatedToken initialToken)
|
||||
: base(initialToken)
|
||||
{
|
||||
#if !NETSTANDARD
|
||||
if (useCache)
|
||||
{
|
||||
Storage = new VssClientCredentialStorage();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Federated;
|
||||
}
|
||||
}
|
||||
|
||||
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
bool isNonAuthenticationChallenge = false;
|
||||
return IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? isNonAuthenticationChallenge;
|
||||
}
|
||||
|
||||
protected override IssuedTokenProvider OnCreateTokenProvider(
|
||||
Uri serverUrl,
|
||||
IHttpResponse response)
|
||||
{
|
||||
// The response is only null when attempting to determine the most appropriate token provider to
|
||||
// use for the connection. The only way we should do anything here is if we have an initial token
|
||||
// since that means we can present something without making a server call.
|
||||
if (response == null && base.InitialToken == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Uri signInUrl = null;
|
||||
String realm = String.Empty;
|
||||
String issuer = String.Empty;
|
||||
|
||||
if (response != null)
|
||||
{
|
||||
var location = response.Headers.GetValues(HttpHeaders.Location).FirstOrDefault();
|
||||
if (location == null)
|
||||
{
|
||||
location = response.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).FirstOrDefault();
|
||||
}
|
||||
|
||||
if (!String.IsNullOrEmpty(location))
|
||||
{
|
||||
signInUrl = new Uri(location);
|
||||
}
|
||||
|
||||
// Inform the server that we support the javascript notify "smart client" pattern for ACS auth
|
||||
AddParameter(ref signInUrl, "protocol", "javascriptnotify");
|
||||
|
||||
// Do not automatically sign in with existing FedAuth cookie
|
||||
AddParameter(ref signInUrl, "force", "1");
|
||||
|
||||
GetRealmAndIssuer(response, out realm, out issuer);
|
||||
}
|
||||
|
||||
return new VssFederatedTokenProvider(this, serverUrl, signInUrl, issuer, realm);
|
||||
}
|
||||
|
||||
internal static void GetRealmAndIssuer(
|
||||
IHttpResponse response,
|
||||
out String realm,
|
||||
out String issuer)
|
||||
{
|
||||
realm = response.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
|
||||
issuer = response.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(issuer))
|
||||
{
|
||||
issuer = new Uri(issuer).GetLeftPart(UriPartial.Authority);
|
||||
}
|
||||
}
|
||||
|
||||
internal static Boolean? IsVssFederatedAuthenticationChallenge(
|
||||
IHttpResponse webResponse,
|
||||
out Boolean isNonAuthenticationChallenge)
|
||||
{
|
||||
isNonAuthenticationChallenge = false;
|
||||
|
||||
if (webResponse == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check to make sure that the redirect was issued from the Tfs service. We include the TfsServiceError
|
||||
// header to avoid the possibility that a redirect from a non-tfs service is issued and we incorrectly
|
||||
// launch the credentials UI.
|
||||
if (webResponse.StatusCode == HttpStatusCode.Found ||
|
||||
webResponse.StatusCode == HttpStatusCode.Redirect)
|
||||
{
|
||||
return webResponse.Headers.GetValues(HttpHeaders.Location).Any() && webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).Any();
|
||||
}
|
||||
else if (webResponse.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
else if (webResponse.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// This is not strictly an "authentication challenge" but it is a state the user can do something about so they can get access to the resource
|
||||
// they are attempting to access. Specifically, the user will hit this when they need to update or create a profile required by business policy.
|
||||
isNonAuthenticationChallenge = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).Any();
|
||||
if (isNonAuthenticationChallenge)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void AddParameter(ref Uri uri, String name, String value)
|
||||
{
|
||||
if (uri.Query.IndexOf(String.Concat(name, "="), StringComparison.OrdinalIgnoreCase) < 0)
|
||||
{
|
||||
UriBuilder builder = new UriBuilder(uri);
|
||||
builder.Query = String.Concat(builder.Query.TrimStart('?'), "&", name, "=", value);
|
||||
uri = builder.Uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using GitHub.Services.Common;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a cookie-based authentication token.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class VssFederatedToken : IssuedToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new <c>VssFederatedToken</c> instance using the specified cookies.
|
||||
/// </summary>
|
||||
/// <param name="cookies"></param>
|
||||
public VssFederatedToken(CookieCollection cookies)
|
||||
{
|
||||
ArgumentUtility.CheckForNull(cookies, "cookies");
|
||||
m_cookies = cookies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the CookieCollection contained within this token. For internal use only.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public CookieCollection CookieCollection
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_cookies;
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override VssCredentialsType CredentialType
|
||||
{
|
||||
get
|
||||
{
|
||||
return VssCredentialsType.Federated;
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ApplyTo(IHttpRequest request)
|
||||
{
|
||||
// From http://www.ietf.org/rfc/rfc2109.txt:
|
||||
// Note: For backward compatibility, the separator in the Cookie header
|
||||
// is semi-colon (;) everywhere.
|
||||
//
|
||||
// HttpRequestHeaders uses comma as the default separator, so instead of returning
|
||||
// a list of cookies, the method returns one semicolon separated string.
|
||||
IEnumerable<String> values = request.Headers.GetValues(s_cookieHeader);
|
||||
request.Headers.SetValue(s_cookieHeader, GetHeaderValue(values));
|
||||
}
|
||||
|
||||
private String GetHeaderValue(IEnumerable<String> cookieHeaders)
|
||||
{
|
||||
List<String> currentCookies = new List<String>();
|
||||
if (cookieHeaders != null)
|
||||
{
|
||||
foreach (String value in cookieHeaders)
|
||||
{
|
||||
currentCookies.AddRange(value.Split(';').Select(x => x.Trim()));
|
||||
}
|
||||
}
|
||||
|
||||
currentCookies.RemoveAll(x => String.IsNullOrEmpty(x));
|
||||
|
||||
foreach (Cookie cookie in m_cookies)
|
||||
{
|
||||
// Remove all existing cookies that match the name of the cookie we are going to add.
|
||||
currentCookies.RemoveAll(x => String.Equals(x.Substring(0, x.IndexOf('=')), cookie.Name, StringComparison.OrdinalIgnoreCase));
|
||||
currentCookies.Add(String.Concat(cookie.Name, "=", cookie.Value));
|
||||
}
|
||||
|
||||
return String.Join("; ", currentCookies);
|
||||
}
|
||||
|
||||
private CookieCollection m_cookies;
|
||||
private static readonly String s_cookieHeader = HttpRequestHeader.Cookie.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using GitHub.Services.Common;
|
||||
using System.Globalization;
|
||||
|
||||
namespace GitHub.Services.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides authentication for internet identities using single-sign-on cookies.
|
||||
/// </summary>
|
||||
internal sealed class VssFederatedTokenProvider : IssuedTokenProvider, ISupportSignOut
|
||||
{
|
||||
public VssFederatedTokenProvider(
|
||||
VssFederatedCredential credential,
|
||||
Uri serverUrl,
|
||||
Uri signInUrl,
|
||||
String issuer,
|
||||
String realm)
|
||||
: base(credential, serverUrl, signInUrl)
|
||||
{
|
||||
Issuer = issuer;
|
||||
Realm = realm;
|
||||
}
|
||||
|
||||
protected override String AuthenticationScheme
|
||||
{
|
||||
get
|
||||
{
|
||||
return "TFS-Federated";
|
||||
}
|
||||
}
|
||||
|
||||
protected override String AuthenticationParameter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrEmpty(this.Issuer) && String.IsNullOrEmpty(this.Realm))
|
||||
{
|
||||
return String.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
return String.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.Issuer, this.Realm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the federated credential from which this provider was created.
|
||||
/// </summary>
|
||||
public new VssFederatedCredential Credential
|
||||
{
|
||||
get
|
||||
{
|
||||
return (VssFederatedCredential)base.Credential;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether or not a call to get token will require interactivity.
|
||||
/// </summary>
|
||||
public override Boolean GetTokenIsInteractive
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.CurrentToken == null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the issuer for the token provider.
|
||||
/// </summary>
|
||||
public String Issuer
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the realm for the token provider.
|
||||
/// </summary>
|
||||
public String Realm
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
protected internal override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
|
||||
{
|
||||
if (!base.IsAuthenticationChallenge(webResponse))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// This means we were proactively constructed without any connection information. In this case
|
||||
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
|
||||
// to retrieve a new token.
|
||||
if (this.SignInUrl == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
String realm, issuer;
|
||||
VssFederatedCredential.GetRealmAndIssuer(webResponse, out realm, out issuer);
|
||||
|
||||
return this.Realm.Equals(realm, StringComparison.OrdinalIgnoreCase) &&
|
||||
this.Issuer.Equals(issuer, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
protected override IssuedToken OnValidatingToken(
|
||||
IssuedToken token,
|
||||
IHttpResponse webResponse)
|
||||
{
|
||||
// If the response has Set-Cookie headers, attempt to retrieve the FedAuth cookie from the response
|
||||
// and replace the current token with the new FedAuth cookie. Note that the server only reissues the
|
||||
// FedAuth cookie if it is issued for more than an hour.
|
||||
CookieCollection fedAuthCookies = CookieUtility.GetFederatedCookies(webResponse);
|
||||
|
||||
if (fedAuthCookies != null)
|
||||
{
|
||||
// The reissued token should have the same user information as the previous one.
|
||||
VssFederatedToken federatedToken = new VssFederatedToken(fedAuthCookies)
|
||||
{
|
||||
Properties = token.Properties,
|
||||
UserId = token.UserId,
|
||||
UserName = token.UserName
|
||||
};
|
||||
|
||||
token = federatedToken;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
public void SignOut(Uri signOutUrl, Uri replyToUrl, String identityProvider)
|
||||
{
|
||||
// The preferred implementation is to follow the signOutUrl with a browser and kill the browser whenever it
|
||||
// arrives at the replyToUrl (or if it bombs out somewhere along the way).
|
||||
// This will work for all Web-based identity providers (Live, Google, Yahoo, Facebook) supported by ACS provided that
|
||||
// the TFS server has registered sign-out urls (in the TF Registry) for each of these.
|
||||
// This is the long-term approach that should be pursued and probably the approach recommended to other
|
||||
// clients which don't have direct access to the cookie store (TEE?)
|
||||
|
||||
// In the short term we are simply going to delete the TFS cookies and the Windows Live cookies that are exposed to this
|
||||
// session. This has the drawback of not properly signing out of Live (you'd still be signed in to e.g. Hotmail, Xbox, MSN, etc.)
|
||||
// but will allow the user to re-enter their live credentials and sign-in again to TFS.
|
||||
// The other drawback is that the clients will have to be updated again when we pursue the implementation outlined above.
|
||||
|
||||
CookieUtility.DeleteFederatedCookies(replyToUrl);
|
||||
if (!String.IsNullOrEmpty(identityProvider) && identityProvider.Equals("Windows Live ID", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
CookieUtility.DeleteWindowsLiveCookies();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user