mirror of
https://github.com/actions/runner.git
synced 2025-12-11 04:46:58 +00:00
173 lines
6.3 KiB
C#
173 lines
6.3 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|