Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fehintolaobafemi/methodanduri #2751

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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; }
}

}