GitHub Actions Runner

This commit is contained in:
Tingluo Huang
2019-10-10 00:52:42 -04:00
commit c8afc84840
1255 changed files with 198670 additions and 0 deletions

View File

@@ -0,0 +1,264 @@
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;
}
}
}

View File

@@ -0,0 +1,95 @@
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);
}
}
}

View File

@@ -0,0 +1,89 @@
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
}
}

View File

@@ -0,0 +1,124 @@
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
}
}

View File

@@ -0,0 +1,77 @@
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);
}
}
}

View File

@@ -0,0 +1,172 @@
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;
}
}
}
}

View File

@@ -0,0 +1,84 @@
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();
}
}

View File

@@ -0,0 +1,157 @@
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();
}
}
}
}