diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index bb48aeaa8..6698f0cd1 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -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"; } diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index ba9662642..fb712d5fb 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -518,7 +518,7 @@ namespace GitHub.Runner.Listener.Configuration Trace.Info(nameof(GetCredentialProvider)); var credentialManager = HostContext.GetService(); - 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); diff --git a/src/Runner.Listener/Configuration/CredentialManager.cs b/src/Runner.Listener/Configuration/CredentialManager.cs index a56f865a6..878cdaa68 100644 --- a/src/Runner.Listener/Configuration/CredentialManager.cs +++ b/src/Runner.Listener/Configuration/CredentialManager.cs @@ -20,7 +20,6 @@ namespace GitHub.Runner.Listener.Configuration { public static readonly Dictionary CredentialTypes = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)}, { Constants.Configuration.OAuth, typeof(OAuthCredential)}, { Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)}, }; diff --git a/src/Runner.Listener/Configuration/CredentialProvider.cs b/src/Runner.Listener/Configuration/CredentialProvider.cs index 7af741b2a..f49e0dea6 100644 --- a/src/Runner.Listener/Configuration/CredentialProvider.cs +++ b/src/Runner.Listener/Configuration/CredentialProvider.cs @@ -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(); - 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) { } diff --git a/src/Runner.Listener/Runner.Listener.csproj b/src/Runner.Listener/Runner.Listener.csproj index 4d73e2a24..3b21c2ffa 100644 --- a/src/Runner.Listener/Runner.Listener.csproj +++ b/src/Runner.Listener/Runner.Listener.csproj @@ -24,7 +24,6 @@ - diff --git a/src/Sdk/AadAuthentication/CookieUtility.cs b/src/Sdk/AadAuthentication/CookieUtility.cs deleted file mode 100644 index ea9975646..000000000 --- a/src/Sdk/AadAuthentication/CookieUtility.cs +++ /dev/null @@ -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 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 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 GetCookieEx(Uri cookiePath, String cookieName) - { - UInt32 flags = INTERNET_COOKIE_HTTPONLY; - - List cookies = new List(); - 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; - } - } -} diff --git a/src/Sdk/AadAuthentication/VssAadCredential.cs b/src/Sdk/AadAuthentication/VssAadCredential.cs deleted file mode 100644 index 92f365d5f..000000000 --- a/src/Sdk/AadAuthentication/VssAadCredential.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Net.Http; -using System.Security; -using GitHub.Services.Common; - -namespace GitHub.Services.Client -{ - /// - /// 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. - /// - [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); - } - } -} diff --git a/src/Sdk/AadAuthentication/VssAadSettings.cs b/src/Sdk/AadAuthentication/VssAadSettings.cs deleted file mode 100644 index 651d642c2..000000000 --- a/src/Sdk/AadAuthentication/VssAadSettings.cs +++ /dev/null @@ -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(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(VssConnectionParameterOverrideKeys.AadNativeClientIdentifier); - return nativeRedirect ?? VssAadSettings.Client; - } - } -#endif - - public static string AadInstance - { - get - { -#if !NETSTANDARD - string aadInstance = VssClientEnvironment.GetSharedConnectedUserValue(VssConnectionParameterOverrideKeys.AadInstance); -#else - string aadInstance = null; -#endif - - if (string.IsNullOrWhiteSpace(aadInstance)) - { - aadInstance = DefaultAadInstance; - } - else if (!aadInstance.EndsWith("/")) - { - aadInstance = aadInstance + "/"; - } - - return aadInstance; - } - } - -#if !NETSTANDARD - /// - /// Application tenant either from a registry override or a constant - /// - public static string ApplicationTenant => - VssClientEnvironment.GetSharedConnectedUserValue(VssConnectionParameterOverrideKeys.AadApplicationTenant) - ?? VssAadSettings.ApplicationTenantId; -#endif - } -} diff --git a/src/Sdk/AadAuthentication/VssAadToken.cs b/src/Sdk/AadAuthentication/VssAadToken.cs deleted file mode 100644 index debfad147..000000000 --- a/src/Sdk/AadAuthentication/VssAadToken.cs +++ /dev/null @@ -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 - } -} diff --git a/src/Sdk/AadAuthentication/VssAadTokenProvider.cs b/src/Sdk/AadAuthentication/VssAadTokenProvider.cs deleted file mode 100644 index a10dbf1b5..000000000 --- a/src/Sdk/AadAuthentication/VssAadTokenProvider.cs +++ /dev/null @@ -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); - } - - /// - /// Temporary implementation since we don't have a good configuration story here at the moment. - /// - protected override Task 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(null); - } - - try - { - return Task.FromResult(GetVssAadToken()); - } - catch - { } - - return Task.FromResult(null); - } - } -} diff --git a/src/Sdk/AadAuthentication/VssFederatedCredential.cs b/src/Sdk/AadAuthentication/VssFederatedCredential.cs deleted file mode 100644 index dcc5bda97..000000000 --- a/src/Sdk/AadAuthentication/VssFederatedCredential.cs +++ /dev/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 -{ - /// - /// Provides federated authentication with a hosted VssConnection instance using cookies. - /// - [Serializable] - public sealed class VssFederatedCredential : FederatedCredential - { - /// - /// Initializes a new VssFederatedCredential instance. - /// - public VssFederatedCredential() - : this(true) - { - } - - /// - /// Initializes a new VssFederatedCredential instance. - /// - public VssFederatedCredential(Boolean useCache) - : this(useCache, null) - { - } - - /// - /// Initializes a new VssFederatedCredential instance. - /// - /// The initial token if available - public VssFederatedCredential(VssFederatedToken initialToken) - : this(false, initialToken) - { - } - - public VssFederatedCredential( - Boolean useCache, - VssFederatedToken initialToken) - : base(initialToken) - { -#if !NETSTANDARD - if (useCache) - { - Storage = new VssClientCredentialStorage(); - } -#endif - } - - /// - /// - /// - 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; - } - } - } -} diff --git a/src/Sdk/AadAuthentication/VssFederatedToken.cs b/src/Sdk/AadAuthentication/VssFederatedToken.cs deleted file mode 100644 index 3e3cbed9d..000000000 --- a/src/Sdk/AadAuthentication/VssFederatedToken.cs +++ /dev/null @@ -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 -{ - /// - /// Provides a cookie-based authentication token. - /// - [Serializable] - public sealed class VssFederatedToken : IssuedToken - { - /// - /// Initializes a new VssFederatedToken instance using the specified cookies. - /// - /// - public VssFederatedToken(CookieCollection cookies) - { - ArgumentUtility.CheckForNull(cookies, "cookies"); - m_cookies = cookies; - } - - /// - /// Returns the CookieCollection contained within this token. For internal use only. - /// - [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 values = request.Headers.GetValues(s_cookieHeader); - request.Headers.SetValue(s_cookieHeader, GetHeaderValue(values)); - } - - private String GetHeaderValue(IEnumerable cookieHeaders) - { - List currentCookies = new List(); - 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(); - } -} diff --git a/src/Sdk/AadAuthentication/VssFederatedTokenProvider.cs b/src/Sdk/AadAuthentication/VssFederatedTokenProvider.cs deleted file mode 100644 index e43f3de4c..000000000 --- a/src/Sdk/AadAuthentication/VssFederatedTokenProvider.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Net; -using System.Net.Http; -using GitHub.Services.Common; -using System.Globalization; - -namespace GitHub.Services.Client -{ - /// - /// Provides authentication for internet identities using single-sign-on cookies. - /// - 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); - } - } - } - - /// - /// Gets the federated credential from which this provider was created. - /// - public new VssFederatedCredential Credential - { - get - { - return (VssFederatedCredential)base.Credential; - } - } - - /// - /// Gets a value indicating whether or not a call to get token will require interactivity. - /// - public override Boolean GetTokenIsInteractive - { - get - { - return this.CurrentToken == null; - } - } - - /// - /// Gets the issuer for the token provider. - /// - public String Issuer - { - get; - private set; - } - - /// - /// Gets the realm for the token provider. - /// - 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(); - } - } - } -}