Skip to content
This repository has been archived by the owner on Jun 30, 2023. It is now read-only.

Commit

Permalink
Add Dsts support in ADAL:
Browse files Browse the repository at this point in the history
1. Add support to whitelist dsts endpoints
2. Add support to request large federated tokens using form_post
  • Loading branch information
Abhinav Bose authored and bgavrilMS committed Jul 17, 2018
1 parent 27bf9fa commit c0f5b4e
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ private DictionaryRequestParameters CreateAuthorizationRequest(string loginHint)
authorizationRequestParameters[OAuthParameter.HasChrome] = "1";
authorizationRequestParameters[OAuthParameter.RedirectUri] = this.redirectUriRequestParameter;

#if DESKTOP
// Added form_post as a way to request to ensure we can handle large requests for dsts scenarios
authorizationRequestParameters[OAuthParameter.ResponseMode] = OAuthResponseModeType.FormPost;
#endif

if (!string.IsNullOrWhiteSpace(loginHint))
{
authorizationRequestParameters[OAuthParameter.LoginHint] = loginHint;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ namespace Microsoft.IdentityModel.Clients.ActiveDirectory.Internal.Helpers
/// </summary>
internal static class EncodingHelper
{
private const int MaxUrlEncodingSize = 2000;

/// <summary>
/// URL encode the given string.
/// </summary>
Expand All @@ -53,8 +55,31 @@ public static string UrlEncode(string message)
return message;
}

message = Uri.EscapeDataString(message);
message = message.Replace("%20", "+");
if (message.Length < MaxUrlEncodingSize)
{
message = Uri.EscapeDataString(message);
message = message.Replace("%20", "+");
}
else
{
StringBuilder sb = new StringBuilder();
int loops = message.Length / MaxUrlEncodingSize;

for (int i = 0; i <= loops; i++)
{
if (i < loops)
{
sb.Append(Uri.EscapeDataString(message.Substring(MaxUrlEncodingSize * i, MaxUrlEncodingSize)));
}
else
{
sb.Append(Uri.EscapeDataString(message.Substring(MaxUrlEncodingSize * i)));
}
}

message = sb.ToString();
message = message.Replace("%20", "+");
}

return message;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ public async Task<IHttpWebResponse> GetResponseAsync()
}
else
{
content = new FormUrlEncodedContent(((DictionaryRequestParameters) this.BodyParameters)
.ToList());
content = new StringContent(this.BodyParameters.ToString(), Encoding.UTF8,
"application/x-www-form-urlencoded");
}

requestMessage.Method = HttpMethod.Post;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ public async Task UpdateFromTemplateAsync(RequestContext requestContext)
{
var authorityUri = new Uri(this.Authority);
var host = authorityUri.Host;
string path = authorityUri.AbsolutePath.Substring(1);
string tenant = path.Substring(0, path.IndexOf("/", StringComparison.Ordinal));
string tenant = authorityUri.Segments[authorityUri.Segments.Length - 1].TrimEnd('/');
if (this.AuthorityType == AuthorityType.AAD)
{
var metadata = await InstanceDiscovery.GetMetadataEntry(authorityUri, this.ValidateAuthority, requestContext).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,19 @@ internal static class InstanceDiscovery
"login.microsoftonline.com" // Microsoft Azure Worldwide
});

private static HashSet<string> WhitelistedDomains = new HashSet<string>(new[]
{
"dsts.core.windows.net",
"dsts.core.chinacloudapi.cn",
"dsts.core.cloudapi.de",
"dsts.core.usgovcloudapi.net",
"dsts.core.azure-test.net"
});

internal static bool IsWhitelisted(string authorityHost)
{
return WhitelistedAuthorities.Contains(authorityHost);
return WhitelistedAuthorities.Contains(authorityHost) ||
WhitelistedDomains.Any(domain => authorityHost.EndsWith(domain, StringComparison.OrdinalIgnoreCase));
}

// The following cache could be private, but we keep it public so that internal unit test can take a peek into it.
Expand Down Expand Up @@ -124,7 +134,20 @@ public static string FormatAuthorizeEndpoint(string host, string tenant)

private static string GetTenant(Uri uri)
{
return uri.AbsolutePath.Split('/')[1]; // Will generate exception when tenant can not be determined
return uri.Segments[uri.Segments.Length - 1].TrimEnd('/');
}

private static string GetHost(Uri uri)
{
if (WhitelistedDomains.Any(domain => uri.Host.EndsWith(domain, StringComparison.OrdinalIgnoreCase)))
{
// Host + Virtual directory
return string.Format(CultureInfo.InvariantCulture, "{0}/{1}", uri.Host, uri.Segments[1].TrimEnd('/'));
}
else
{
return uri.Host;
}
}

// No return value. Modifies InstanceCache directly.
Expand All @@ -133,7 +156,7 @@ private static async Task DiscoverAsync(Uri authority, bool validateAuthority, R
string instanceDiscoveryEndpoint = string.Format(
CultureInfo.InvariantCulture,
"https://{0}/common/discovery/instance?api-version=1.1&authorization_endpoint={1}",
WhitelistedAuthorities.Contains(authority.Host) ? authority.Host : DefaultTrustedAuthority,
IsWhitelisted(authority.Host) ? GetHost(authority) : DefaultTrustedAuthority,
FormatAuthorizeEndpoint(authority.Host, GetTenant(authority)));
var client = new AdalHttpClient(instanceDiscoveryEndpoint, requestContext);
InstanceDiscoveryResponse discoveryResponse = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ internal static class OAuthParameter
public const string LoginHint = "login_hint"; // login_hint is not standard oauth2 parameter
public const string CorrelationId = OAuthHeader.CorrelationId; // correlation id is not standard oauth2 parameter
public const string Prompt = "prompt"; // prompt is not standard oauth2 parameter

public const string ResponseMode = "response_mode";
}

internal static class OAuthGrantType
Expand All @@ -68,6 +70,11 @@ internal static class OAuthResponseType
public const string Code = "code";
}

internal static class OAuthResponseModeType
{
public const string FormPost = "form_post";
}

internal static class OAuthAssertionType
{
public const string JwtBearer = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ protected virtual void WebBrowserNavigatingHandler(object sender, WebBrowserNavi
// we cancel further processing, if we reached final URL.
// Security issue: we prohibit navigation with auth code
// if redirect URI is URN, then we prohibit navigation, to prevent random browser popup.
e.Cancel = this.CheckForClosingUrl(e.Url);
e.Cancel = this.CheckForClosingUrl(sender, e.Url);

// check if the url scheme is of type browser://
// this means we need to launch external browser
Expand All @@ -177,7 +177,7 @@ protected virtual void WebBrowserNavigatingHandler(object sender, WebBrowserNavi

private void WebBrowserNavigatedHandler(object sender, WebBrowserNavigatedEventArgs e)
{
this.CheckForClosingUrl(e.Url);
this.CheckForClosingUrl(sender, e.Url);
}

/// <summary>
Expand Down Expand Up @@ -217,14 +217,14 @@ protected virtual void WebBrowserNavigateErrorHandler(object sender, WebBrowserN
this.OnNavigationCanceled(e.StatusCode);
}

private bool CheckForClosingUrl(Uri url)
private bool CheckForClosingUrl(object sender, Uri url)
{
// Make change here
bool canClose = false;
if (url.Authority.Equals(this.desiredCallbackUri.Authority, StringComparison.OrdinalIgnoreCase) &&
url.AbsolutePath.Equals(this.desiredCallbackUri.AbsolutePath, StringComparison.OrdinalIgnoreCase))
{
this.Result = new AuthorizationResult(AuthorizationStatus.Success, url.OriginalString);
this.Result = new AuthorizationResult(AuthorizationStatus.Success, GetAuthenticationCode(((CustomWebBrowser)sender).Document));
canClose = true;
}

Expand All @@ -251,6 +251,27 @@ private bool CheckForClosingUrl(Uri url)
return canClose;
}

private string GetAuthenticationCode(HtmlDocument document)
{
string result = null;
HtmlElementCollection elems = document.GetElementsByTagName("input");
foreach(HtmlElement elem in elems)
{
string name = elem.GetAttribute("name");
if(!string.IsNullOrEmpty(name))
{
if(string.Equals(name, "code", StringComparison.OrdinalIgnoreCase))
{
result = elem.GetAttribute("value");
result = Constants.FormPostPrefix + result;
break;
}
}
}

return result;
}

private void StopWebBrowser()
{
if (!this.webBrowser.IsDisposed && this.webBrowser.IsBusy)
Expand Down
1 change: 1 addition & 0 deletions core/src/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ internal static class Constants
public const int ExpirationMarginInMinutes = 5;
public const int CodeVerifierLength = 128;
public const int CodeVerifierByteSize = 32;
public const string FormPostPrefix = "formpost://";
}


Expand Down
7 changes: 7 additions & 0 deletions core/src/UI/AuthorizationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ internal AuthorizationResult(AuthorizationStatus status)

public void ParseAuthorizeResponse(string webAuthenticationResult)
{
if (!string.IsNullOrWhiteSpace(webAuthenticationResult) &&
webAuthenticationResult.StartsWith(Constants.FormPostPrefix, StringComparison.OrdinalIgnoreCase))
{
this.Code = webAuthenticationResult.Substring(Constants.FormPostPrefix.Length);
return;
}

var resultUri = new Uri(webAuthenticationResult);

// NOTE: The Fragment property actually contains the leading '#' character and that must be dropped
Expand Down

0 comments on commit c0f5b4e

Please sign in to comment.