Skip to content

Commit

Permalink
Fehintolaobafemi/methodanduri (#2751)
Browse files Browse the repository at this point in the history
* Making changes to how httpmethod and uri is processed

---------

Co-authored-by: Tim <timwamalwa@gmail.com>
Co-authored-by: Peter Ombwa <peter.ombwa@microsoft.com>
Co-authored-by: Peter Ombwa <peombwa@microsoft.com>
Co-authored-by: Mustafa Zengin <mzengin88@gmail.com>
Co-authored-by: Clément Notin <cnotin@tenable.com>
Co-authored-by: Microsoft Graph DevX Tooling <GraphTooling@service.microsoft.com>
Co-authored-by: Vincent Biret <vincentbiret@hotmail.com>
Co-authored-by: Vincent Biret <vibiret@microsoft.com>
Co-authored-by: Subhajit Ray (from Dev Box) <subray@microsoft.com>
  • Loading branch information
10 people committed Jun 5, 2024
1 parent 9ec61a5 commit 369c26c
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 45 deletions.
5 changes: 5 additions & 0 deletions src/Authentication/Authentication.Core/Common/GraphSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public class GraphSession : IGraphSession
/// </summary>
public IGraphOption GraphOption { get; set; }

/// <summary>
/// Temporarily stores the user's Graph request details such as Method and Uri. Essential as part of the Proof of Possession efforts.
/// </summary>
public IGraphRequestProofofPossession GraphRequestProofofPossession { get; set; }

/// <summary>
/// Represents a collection of Microsoft Graph PowerShell meta-info.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using Azure.Core;
using Azure.Identity;
using System;
using System.Net.Http;

namespace Microsoft.Graph.PowerShell.Authentication
{
public interface IGraphRequestProofofPossession
{
Uri Uri { get; set; }
HttpMethod HttpMethod { get; set; }
AccessToken AccessToken { get; set; }
string ProofofPossessionNonce { get; set; }
PopTokenRequestContext PopTokenContext { get; set; }
Request Request { get; set; }
InteractiveBrowserCredential BrowserCredential { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public interface IGraphSession
IDataStore DataStore { get; set; }
IRequestContext RequestContext { get; set; }
IGraphOption GraphOption { get; set; }
IGraphRequestProofofPossession GraphRequestProofofPossession { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.12.0-beta.1" />
<PackageReference Include="Azure.Core" Version="1.39.0" />
<PackageReference Include="Azure.Identity.Broker" Version="1.1.0" />
<PackageReference Include="Azure.Core.Experimental" Version="0.1.0-preview.33" />
<PackageReference Include="Azure.Identity.Broker" Version="1.2.0-beta.1" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.1.10" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.60.3" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.60.3" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------
using Azure;
using Azure.Core;
using Azure.Core.Diagnostics;
using Azure.Core.Pipeline;
Expand All @@ -16,6 +17,7 @@
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -91,7 +93,7 @@ private static bool IsWamSupported()
}

//Check to see if ATPoP is Supported
private static bool IsATPoPSupported()
public static bool IsATPoPSupported()
{
return GraphSession.Instance.GraphOption.EnableATPoPForMSGraph;
}
Expand Down Expand Up @@ -131,10 +133,17 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
interactiveOptions.AuthorityHost = new Uri(GetAuthorityUrl(authContext));
interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext);

var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
if (IsATPoPSupported())
{
GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext = CreatePopTokenRequestContext(authContext);
GraphSession.Instance.GraphRequestProofofPossession.BrowserCredential = interactiveBrowserCredential;
}

if (!File.Exists(Constants.AuthRecordPath))
{
AuthenticationRecord authRecord;
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
//var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
if (IsWamSupported())
{
// Adding a scenario to account for Access Token Proof of Possession
Expand All @@ -143,45 +152,9 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
// Logic to implement ATPoP Authentication
authRecord = await Task.Run(() =>
{
// Creating a Request to retrieve nonce value
string popNonce = null;
var popNonceToken = "nonce=\"";
Uri resourceUri = new Uri("https://canary.graph.microsoft.com/beta/me"); //PPE (https://graph.microsoft-ppe.com) or Canary (https://canary.graph.microsoft.com) or (https://20.190.132.47/beta/me)
HttpClient httpClient = new(new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true });
HttpResponseMessage response = httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, resourceUri)).Result;
// Find the WWW-Authenticate header in the response.
var popChallenge = response.Headers.WwwAuthenticate.First(wa => wa.Scheme == "PoP");
var nonceStart = popChallenge.Parameter.IndexOf(popNonceToken) + popNonceToken.Length;
var nonceEnd = popChallenge.Parameter.IndexOf('"', nonceStart);
popNonce = popChallenge.Parameter.Substring(nonceStart, nonceEnd - nonceStart);
// Refresh token logic --- start
var popTokenAuthenticationPolicy = new PopTokenAuthenticationPolicy(interactiveBrowserCredential as ISupportsProofOfPossession, $"https://graph.microsoft.com/.default");
var pipelineOptions = new HttpPipelineOptions(new PopClientOptions()
{
Diagnostics =
{
IsLoggingContentEnabled = true,
LoggedHeaderNames = { "Authorization" }
},
});
pipelineOptions.PerRetryPolicies.Add(popTokenAuthenticationPolicy);
var _pipeline = HttpPipelineBuilder.Build(pipelineOptions, new HttpPipelineTransportOptions { ServerCertificateCustomValidationCallback = (_) => true });
using var request = _pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(resourceUri);
// Manually invoke the authentication policy's process method
popTokenAuthenticationPolicy.ProcessAsync(new HttpMessage(request, new ResponseClassifier()), ReadOnlyMemory<HttpPipelinePolicy>.Empty);
// Refresh token logic --- end
// Run the thread in MTA.
var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: popNonce, request: request);
//var token = interactiveBrowserCredential.GetToken(popContext, cancellationToken);
return interactiveBrowserCredential.Authenticate(popContext, cancellationToken);
//GraphSession.Instance.GraphRequestProofofPossession.AccessToken = interactiveBrowserCredential.GetTokenAsync(GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext, cancellationToken).Result;
return interactiveBrowserCredential.AuthenticateAsync(GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext, cancellationToken);
});
}
else
Expand Down Expand Up @@ -508,6 +481,64 @@ public static Task DeleteAuthRecordAsync()
File.Delete(Constants.AuthRecordPath);
return Task.CompletedTask;
}

public static PopTokenRequestContext CreatePopTokenRequestContext(IAuthContext authContext)
{
// Creating a httpclient that would handle all pop calls
Uri popResourceUri = GraphSession.Instance.GraphRequestProofofPossession.Uri ?? new Uri("https://canary.graph.microsoft.com/beta/me"); //PPE (https://graph.microsoft-ppe.com) or Canary (https://canary.graph.microsoft.com) or (https://20.190.132.47/beta/me)
HttpClient popHttpClient = new(new HttpClientHandler { ServerCertificateCustomValidationCallback = (_, _, _, _) => true });

// Find the WWW-Authenticate header in the response.
var popMethod = GraphSession.Instance.GraphRequestProofofPossession.HttpMethod ?? HttpMethod.Get;
var popResponse = popHttpClient.SendAsync(new HttpRequestMessage(popMethod, popResourceUri)).Result;
var popChallenge = popResponse.Headers.WwwAuthenticate.First(wa => wa.Scheme == "PoP");
var nonceStart = popChallenge.Parameter.IndexOf("nonce=\"") + "nonce=\"".Length;
var nonceEnd = popChallenge.Parameter.IndexOf('"', nonceStart);
GraphSession.Instance.GraphRequestProofofPossession.ProofofPossessionNonce = popChallenge.Parameter.Substring(nonceStart, nonceEnd - nonceStart);

// Refresh token logic --- start
var popPipelineOptions = new HttpPipelineOptions(new PopClientOptions()
{

});

var _popPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions { ServerCertificateCustomValidationCallback = (_) => true });
GraphSession.Instance.GraphRequestProofofPossession.Request = _popPipeline.CreateRequest();
GraphSession.Instance.GraphRequestProofofPossession.Request.Method = ConvertToAzureRequestMethod(popMethod);
GraphSession.Instance.GraphRequestProofofPossession.Request.Uri.Reset(popResourceUri);

// Refresh token logic --- end
var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: GraphSession.Instance.GraphRequestProofofPossession.ProofofPossessionNonce, request: GraphSession.Instance.GraphRequestProofofPossession.Request);
return popContext;
}
public static RequestMethod ConvertToAzureRequestMethod(HttpMethod httpMethod)
{
// Mapping known HTTP methods
switch (httpMethod.Method.ToUpper())
{
case "GET":
return RequestMethod.Get;
case "POST":
return RequestMethod.Post;
case "PUT":
return RequestMethod.Put;
case "DELETE":
return RequestMethod.Delete;
case "HEAD":
return RequestMethod.Head;
case "OPTIONS":
return RequestMethod.Options;
case "PATCH":
return RequestMethod.Patch;
case "TRACE":
return RequestMethod.Trace;
default:
throw new ArgumentException($"Unsupported HTTP method: {httpMethod.Method}");
}
}
}
internal class PopClientOptions : ClientOptions
{
}
internal class PopClientOptions : ClientOptions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,8 @@ private async Task ProcessRecordAsync()
try
{
PrepareSession();
GraphSession.Instance.GraphRequestProofofPossession.Uri = Uri;
GraphSession.Instance.GraphRequestProofofPossession.HttpMethod = GetHttpMethod(Method);
var client = HttpHelpers.GetGraphHttpClient();
ValidateRequestUri();
using (var httpRequestMessage = GetRequest(client, Uri))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null)
{
DataStore = dataStore ?? new DiskDataStore(),
RequestContext = new RequestContext(),
GraphOption = graphOptions ?? new GraphOption()
GraphOption = graphOptions ?? new GraphOption(),
GraphRequestProofofPossession = new GraphRequestProofofPossession()
};
}
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
// ------------------------------------------------------------------------------


using Azure.Core;
using Azure.Identity;
using Azure.Identity.Broker;
using Microsoft.Graph.Authentication;
using Microsoft.Graph.PowerShell.Authentication.Core.Utilities;
using Microsoft.Graph.PowerShell.Authentication.Extensions;
using Microsoft.Identity.Client;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -63,9 +68,24 @@ private async Task AuthenticateRequestAsync(HttpRequestMessage httpRequestMessag
{
if (AuthenticationProvider != null)
{
var accessToken = await AuthenticationProvider.GetAuthorizationTokenAsync(httpRequestMessage.RequestUri, additionalAuthenticationContext, cancellationToken: cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(accessToken))
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, accessToken);
if (AuthenticationHelpers.IsATPoPSupported())
{
GraphSession.Instance.GraphRequestProofofPossession.Request.Method = AuthenticationHelpers.ConvertToAzureRequestMethod(httpRequestMessage.Method);
GraphSession.Instance.GraphRequestProofofPossession.Request.Uri.Reset(httpRequestMessage.RequestUri);
foreach (var header in httpRequestMessage.Headers)
{
GraphSession.Instance.GraphRequestProofofPossession.Request.Headers.Add(header.Key, header.Value.First());
}

var accessToken = GraphSession.Instance.GraphRequestProofofPossession.BrowserCredential.GetTokenAsync(GraphSession.Instance.GraphRequestProofofPossession.PopTokenContext, cancellationToken).Result;
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue("Pop", accessToken.Token);
}
else
{
var accessToken = await AuthenticationProvider.GetAuthorizationTokenAsync(httpRequestMessage.RequestUri, additionalAuthenticationContext, cancellationToken: cancellationToken).ConfigureAwait(false);
if (!string.IsNullOrEmpty(accessToken))
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, accessToken);
}
}
}

Expand All @@ -87,6 +107,14 @@ private async Task<HttpResponseMessage> SendRetryAsync(HttpResponseMessage httpR
}
await DrainAsync(httpResponseMessage).ConfigureAwait(false);

if (AuthenticationHelpers.IsATPoPSupported())
{
var popChallenge = httpResponseMessage.Headers.WwwAuthenticate.First(wa => wa.Scheme == "PoP");
var nonceStart = popChallenge.Parameter.IndexOf("nonce=\"") + "nonce=\"".Length;
var nonceEnd = popChallenge.Parameter.IndexOf('"', nonceStart);
GraphSession.Instance.GraphRequestProofofPossession.ProofofPossessionNonce = popChallenge.Parameter.Substring(nonceStart, nonceEnd - nonceStart);
}

// Authenticate request using auth provider
await AuthenticateRequestAsync(newRequest, additionalRequestInfo, cancellationToken).ConfigureAwait(false);
httpResponseMessage = await base.SendAsync(newRequest, cancellationToken);
Expand Down
4 changes: 4 additions & 0 deletions src/Authentication/Authentication/Helpers/HttpHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------
using Azure.Core;
using Microsoft.Graph.Authentication;
using Microsoft.Graph.PowerShell.Authentication.Core.Interfaces;
using Microsoft.Graph.PowerShell.Authentication.Core.Utilities;
using Microsoft.Graph.PowerShell.Authentication.Handlers;
using Microsoft.Identity.Client;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware;
using Microsoft.Kiota.Http.HttpClientLibrary.Middleware.Options;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace Microsoft.Graph.PowerShell.Authentication.Helpers
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using Azure.Core;
using Azure.Identity;
using System;
using System.IO;
using System.Net.Http;

namespace Microsoft.Graph.PowerShell.Authentication
{
internal class GraphRequestProofofPossession : IGraphRequestProofofPossession
{
public Uri Uri { get; set; }
public HttpMethod HttpMethod { get; set; }
public AccessToken AccessToken { get; set; }
public string ProofofPossessionNonce { get; set; }
public PopTokenRequestContext PopTokenContext { get; set; }
public Request Request { get; set; }
public InteractiveBrowserCredential BrowserCredential { get; set; }
}

}

0 comments on commit 369c26c

Please sign in to comment.